From efd4fbd04937933d1f2ffde1c4654644910faff1 Mon Sep 17 00:00:00 2001 From: Richard Thompson Date: Tue, 23 Apr 2024 11:54:34 +0100 Subject: [PATCH] Initial version of package An initial version of an Umbraco package to remove any personal data stored on a site. --- .editorconfig | 8 + .github/README.md | 128 +++++ .github/workflows/release.yml | 29 ++ .gitignore | 16 + LICENSE | 21 + docs/README_nuget.md | 11 + docs/assets/img/logo.png | Bin 0 -> 27358 bytes src/.editorconfig | 403 +++++++++++++++ src/Sanitiser.TestSite.V10/.gitignore | 481 ++++++++++++++++++ src/Sanitiser.TestSite.V10/Program.cs | 22 + .../Properties/launchSettings.json | 29 ++ .../Sanitiser.TestSite.v10.csproj | 27 + src/Sanitiser.TestSite.V10/Startup.cs | 64 +++ .../Views/Partials/blockgrid/area.cshtml | 9 + .../Views/Partials/blockgrid/areas.cshtml | 15 + .../Views/Partials/blockgrid/default.cshtml | 13 + .../Views/Partials/blockgrid/items.cshtml | 37 ++ .../Views/Partials/blocklist/default.cshtml | 20 + .../Partials/grid/bootstrap3-fluid.cshtml | 109 ++++ .../Views/Partials/grid/bootstrap3.cshtml | 115 +++++ .../Views/Partials/grid/editors/base.cshtml | 28 + .../Views/Partials/grid/editors/embed.cshtml | 11 + .../Views/Partials/grid/editors/macro.cshtml | 15 + .../Views/Partials/grid/editors/media.cshtml | 60 +++ .../Views/Partials/grid/editors/rte.cshtml | 13 + .../Partials/grid/editors/textstring.cshtml | 22 + .../Views/_ViewImports.cshtml | 10 + .../appsettings.Development.json | 34 ++ src/Sanitiser.TestSite.V10/appsettings.json | 50 ++ .../umbraco/Licenses/umbracoForms.lic | 1 + .../umbraco/models/File.generated.cs | 75 +++ .../umbraco/models/Folder.generated.cs | 52 ++ .../umbraco/models/Image.generated.cs | 89 ++++ .../umbraco/models/Member.generated.cs | 60 +++ .../models/UmbracoMediaArticle.generated.cs | 75 +++ .../models/UmbracoMediaAudio.generated.cs | 75 +++ .../UmbracoMediaVectorGraphics.generated.cs | 75 +++ .../models/UmbracoMediaVideo.generated.cs | 75 +++ .../wwwroot/favicon.ico | Bin 0 -> 15406 bytes src/Sanitiser.sln | 40 ++ .../Collections/SanitisersCollection.cs | 6 + .../SanitisersCollectionBuilder.cs | 11 + .../Configuration/MembersSanitiserOptions.cs | 8 + .../Configuration/SanitiserOptions.cs | 9 + .../UmbracoFormsSanitisiationOptions.cs | 6 + .../Models/UmbracoCacheInstruction.cs | 9 + src/Sanitiser/Sanitiser.csproj | 43 ++ src/Sanitiser/SanitiserComposer.cs | 25 + src/Sanitiser/SanitiserManifestFilter.cs | 19 + .../Sanitisers/DatabaseTableSanitiser.cs | 44 ++ src/Sanitiser/Sanitisers/ISanitiser.cs | 10 + src/Sanitiser/Sanitisers/MembersSanitiser.cs | 70 +++ .../Sanitisers/UmbracoFormsSanitiser.cs | 51 ++ .../SanitizationStartupNotification.cs | 15 + .../Services/ISanitisationService.cs | 10 + src/Sanitiser/Services/SanitizationService.cs | 32 ++ src/Sanitiser/WebCompositionExtensions.cs | 12 + .../appsettings-schema.Sanitiser.json | 47 ++ src/Sanitiser/wwwroot/Sanitiser/lang/en.xml | 5 + umbraco-marketplace.json | 16 + 60 files changed, 2865 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/README.md create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 docs/README_nuget.md create mode 100644 docs/assets/img/logo.png create mode 100644 src/.editorconfig create mode 100644 src/Sanitiser.TestSite.V10/.gitignore create mode 100644 src/Sanitiser.TestSite.V10/Program.cs create mode 100644 src/Sanitiser.TestSite.V10/Properties/launchSettings.json create mode 100644 src/Sanitiser.TestSite.V10/Sanitiser.TestSite.v10.csproj create mode 100644 src/Sanitiser.TestSite.V10/Startup.cs create mode 100644 src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/area.cshtml create mode 100644 src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/areas.cshtml create mode 100644 src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/default.cshtml create mode 100644 src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/items.cshtml create mode 100644 src/Sanitiser.TestSite.V10/Views/Partials/blocklist/default.cshtml create mode 100644 src/Sanitiser.TestSite.V10/Views/Partials/grid/bootstrap3-fluid.cshtml create mode 100644 src/Sanitiser.TestSite.V10/Views/Partials/grid/bootstrap3.cshtml create mode 100644 src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/base.cshtml create mode 100644 src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/embed.cshtml create mode 100644 src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/macro.cshtml create mode 100644 src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/media.cshtml create mode 100644 src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/rte.cshtml create mode 100644 src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/textstring.cshtml create mode 100644 src/Sanitiser.TestSite.V10/Views/_ViewImports.cshtml create mode 100644 src/Sanitiser.TestSite.V10/appsettings.Development.json create mode 100644 src/Sanitiser.TestSite.V10/appsettings.json create mode 100644 src/Sanitiser.TestSite.V10/umbraco/Licenses/umbracoForms.lic create mode 100644 src/Sanitiser.TestSite.V10/umbraco/models/File.generated.cs create mode 100644 src/Sanitiser.TestSite.V10/umbraco/models/Folder.generated.cs create mode 100644 src/Sanitiser.TestSite.V10/umbraco/models/Image.generated.cs create mode 100644 src/Sanitiser.TestSite.V10/umbraco/models/Member.generated.cs create mode 100644 src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaArticle.generated.cs create mode 100644 src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaAudio.generated.cs create mode 100644 src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaVectorGraphics.generated.cs create mode 100644 src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaVideo.generated.cs create mode 100644 src/Sanitiser.TestSite.V10/wwwroot/favicon.ico create mode 100644 src/Sanitiser.sln create mode 100644 src/Sanitiser/Collections/SanitisersCollection.cs create mode 100644 src/Sanitiser/Collections/SanitisersCollectionBuilder.cs create mode 100644 src/Sanitiser/Configuration/MembersSanitiserOptions.cs create mode 100644 src/Sanitiser/Configuration/SanitiserOptions.cs create mode 100644 src/Sanitiser/Configuration/UmbracoFormsSanitisiationOptions.cs create mode 100644 src/Sanitiser/Models/UmbracoCacheInstruction.cs create mode 100644 src/Sanitiser/Sanitiser.csproj create mode 100644 src/Sanitiser/SanitiserComposer.cs create mode 100644 src/Sanitiser/SanitiserManifestFilter.cs create mode 100644 src/Sanitiser/Sanitisers/DatabaseTableSanitiser.cs create mode 100644 src/Sanitiser/Sanitisers/ISanitiser.cs create mode 100644 src/Sanitiser/Sanitisers/MembersSanitiser.cs create mode 100644 src/Sanitiser/Sanitisers/UmbracoFormsSanitiser.cs create mode 100644 src/Sanitiser/SanitizationStartupNotification.cs create mode 100644 src/Sanitiser/Services/ISanitisationService.cs create mode 100644 src/Sanitiser/Services/SanitizationService.cs create mode 100644 src/Sanitiser/WebCompositionExtensions.cs create mode 100644 src/Sanitiser/appsettings-schema.Sanitiser.json create mode 100644 src/Sanitiser/wwwroot/Sanitiser/lang/en.xml create mode 100644 umbraco-marketplace.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..30b3610 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..6af9c55 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,128 @@ +# Sanitiser + +[![Downloads](https://img.shields.io/nuget/dt/Umbraco.Community.Sanitiser?color=cc9900)](https://www.nuget.org/packages/Umbraco.Community.Sanitiser/) +[![NuGet](https://img.shields.io/nuget/vpre/Umbraco.Community.Sanitiser?color=0273B3)](https://www.nuget.org/packages/Umbraco.Community.Sanitiser) +[![GitHub license](https://img.shields.io/github/license/richarth/sanitiser?color=8AB803)](https://github.com/richarth/sanitiser/blob/main/LICENSE) + +When enabled, this package will automatically remove personal data from your Umbraco website on startup. + +Out of the box the package will delete member data and form submissions. + +Umbraco versions supported: v10.8.5+ + +## Installation + +Add the package to your Umbraco website from nuget: + +`dotnet add package Umbraco.Community.Sanitiser` + +## Configuration + +To enable the package, add the following to your `appsettings.json`: + +```json +{ + "Sanitiser": { + "Enabled": true + } +} +``` + +### Member Data + +To enable the deletion of member data, add the following to your `appsettings.json`: + +```json +{ + "Sanitiser": { + "Enabled": true, + "MembersSanitiser": { + "Enable": true + } + } +} +``` + +To exclude members whose email addresses belong to specific domains from deletion, add the following to your `appsettings.json`: + +```json +{ + "Sanitiser": { + "Enabled": true, + "MembersSanitiser": { + "Enable": true, + "DomainsToExclude": "test.com,example.com" + } + } +} +``` + +### Umbraco Forms Submissions + +To enable the deletion of form submissions, add the following to your `appsettings.json`: + +```json +{ + "Sanitiser": { + "Enabled": true, + "UmbracoFormsSanitiser": { + "Enable": true + } + } +} +``` + +### Custom database tables + +To enable the deletion of data from custom database tables, you can extend the `DatabaseTableSanitiser` class and create a poco with a table name attribute. + +For example, to have a table called `test` automatically emptied on startup, create a poco like this: + +```csharp +using NPoco; + +[TableName("test")] +public class Test; +``` + +And extend the `DatabaseTableSanitiser` class like this with your poco class as a type parameter: + +```csharp +using Umbraco.Cms.Infrastructure.Scoping; +using Umbraco.Community.Sanitiser.Models; + +namespace Umbraco.Community.Sanitiser.sanitisers; + +public class TestTableSanitiser(IScopeProvider scopeProvider) : DatabaseTableSanitiser(scopeProvider) +{ + public override bool IsEnabled() + { + return true; + } +} +``` + +> [!WARNING] + +N.B. This package is not intended to be run on production sites, only enable sanitization on a development or staging environment. Before enabling please ensure you have a backup of your data and a backup of your backup. + +If there is a lot of data then the startup of your site may be delayed. Only enable when necessary. + +## Customisation + +To add your own sanitization logic, implement the `ISanitiser` interface. Your sanitization logic will be run automatically on startup when the sanitization service and your sanitizer are enabled. + +Add your logic to the `Sanitise` method. + +You will also need to implement the enabled check in the `IsEnabled` method. You could check for a value in Umbraco, simply return true or more likely add a setting to `appsettings.json`. + +If adding your own setting to the `Sanitiser` section of `appsettings.json`, you can add a new class which extends the `SanitiserOptions` class to include your new setting. The values from your `appsettings.json` will be automatically mapped to your new class. + +## Acknowledgements + +### Logo + +The package logo uses the [Sanitiser](https://thenounproject.com/icon/sanitiser-6216442/) (by [Manish Mittal](https://thenounproject.com/creator/butterfingers/)) icon from the [Noun Project](https://thenounproject.com), licensed under [CC BY 3.0 US](https://creativecommons.org/licenses/by/3.0/us/). + +## License +MIT License diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fa0ad68 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,29 @@ +name: Release Package + +on: + push: + branches: + - main + tags: + - "[0-9]+.[0-9]+.[0-9]+" + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Build project + run: dotnet build src\Sanitiser\Sanitiser.csproj --configuration Release + + - name: Push to NuGet + run: dotnet nuget push **\*.nupkg --api-key ${{secrets.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..14937af --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.vs/ + +.idea/ + +*.user + +.DS_Store + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/tools/ +[Oo]bj/ +node_modules/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fd73aa5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Richard Thompson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/README_nuget.md b/docs/README_nuget.md new file mode 100644 index 0000000..0763758 --- /dev/null +++ b/docs/README_nuget.md @@ -0,0 +1,11 @@ +# Sanitiser + +[![Downloads](https://img.shields.io/nuget/dt/Umbraco.Community.Sanitiser?color=cc9900)](https://www.nuget.org/packages/Umbraco.Community.Sanitiser/) +[![NuGet](https://img.shields.io/nuget/vpre/Umbraco.Community.Sanitiser?color=0273B3)](https://www.nuget.org/packages/Umbraco.Community.Sanitiser) +[![GitHub license](https://img.shields.io/github/license/richarth/sanitiser?color=8AB803)](https://github.com/richarth/sanitiser/blob/main/LICENSE) + +When enabled, this package will automatically remove personal data from your Umbraco website on startup. + +Out of the box the package will delete member data and form submissions. + +Umbraco versions supported: v10.8.5+ diff --git a/docs/assets/img/logo.png b/docs/assets/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0d41d5b375ef1ee44ee0c66709e45aac8f6938a9 GIT binary patch literal 27358 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
    )k-+0p%*#>ak@108ea%30d?DgAx2`687{>@`v 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 N!L<#wYVX-U`+t@_#Fzj8 literal 0 HcmV?d00001 diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 0000000..2a79fd0 --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,403 @@ +# This .editorconfig has been taken from Umbraco CMS, licensed under MIT. +# https://raw.githubusercontent.com/umbraco/Umbraco-CMS/release-10.2.0/.editorconfig + +# Version: 1.6.2 (Using https://semver.org/) +# Updated: 2020-11-02 +# See https://github.com/RehanSaeed/EditorConfig/releases for release notes. +# See https://github.com/RehanSaeed/EditorConfig for updates to this file. +# See http://EditorConfig.org for more information about .editorconfig files. + +########################################## +# Common Settings +########################################## + +# This file is the top-most EditorConfig file +root = true + +# All Files +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +########################################## +# File Extension Settings +########################################## + +# Visual Studio Solution Files +[*.sln] +indent_style = tab + +# Visual Studio XML Project Files +[*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML Configuration Files +[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}] +indent_size = 2 + +# JSON Files +[*.{json,json5,webmanifest}] +indent_size = 2 + +# YAML Files +[*.{yml,yaml}] +indent_size = 2 + +# Markdown Files +[*.md] +trim_trailing_whitespace = false + +# Web Files +[*.{htm,html,js,jsm,ts,tsx,css,sass,scss,less,svg,vue}] +indent_size = 2 + +# Batch Files +[*.{cmd,bat}] +end_of_line = crlf + +# Bash Files +[*.sh] +end_of_line = lf + +# Makefiles +[Makefile] +indent_style = tab + + +[*.js] +trim_trailing_whitespace = true + +[*.less] +trim_trailing_whitespace = false + +########################################## +# File Header (Uncomment to support file headers) +# https://docs.microsoft.com/visualstudio/ide/reference/add-file-header +########################################## + +# [*.{cs,csx,cake,vb,vbx}] +file_header_template = Copyright (c) Umbraco.\nSee LICENSE for more details. + +# SA1636: File header copyright text should match +# Justification: .editorconfig supports file headers. If this is changed to a value other than "none", a stylecop.json file will need to added to the project. +# dotnet_diagnostic.SA1636.severity = none + +########################################## +# .NET Language Conventions +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions +########################################## + +# .NET Code Style Settings +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#net-code-style-settings +[*.{cs,csx,cake,vb,vbx}] +# "this." and "Me." qualifiers +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#this-and-me +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion +# Language keywords instead of framework type names for type references +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#language-keywords +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning +# Modifier preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#normalize-modifiers +dotnet_style_require_accessibility_modifiers = always:warning +csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:warning +visual_basic_preferred_modifier_order = Partial, Default, Private, Protected, Public, Friend, NotOverridable, Overridable, MustOverride, Overloads, Overrides, MustInherit, NotInheritable, Static, Shared, Shadows, ReadOnly, WriteOnly, Dim, Const, WithEvents, Widening, Narrowing, Custom, Async:warning +dotnet_style_readonly_field = true:warning +# Parentheses preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parentheses-preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion +# Expression-level preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_prefer_inferred_tuple_names = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion +dotnet_style_prefer_conditional_expression_over_return = false:suggestion +dotnet_style_prefer_compound_assignment = true:warning +# Null-checking preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#null-checking-preferences +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +# Parameter preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parameter-preferences +dotnet_code_quality_unused_parameters = all:warning +# More style options (Undocumented) +# https://github.com/MicrosoftDocs/visualstudio-docs/issues/3641 +dotnet_style_operator_placement_when_wrapping = end_of_line +# https://github.com/dotnet/roslyn/pull/40070 +dotnet_style_prefer_simplified_interpolation = true:warning + +# C# Code Style Settings +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-code-style-settings +[*.{cs,csx,cake}] +# Implicit and explicit types +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#implicit-and-explicit-types +csharp_style_var_for_built_in_types = never +csharp_style_var_when_type_is_apparent = false +csharp_style_var_elsewhere = false +# Expression-bodied members +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-bodied-members +csharp_style_expression_bodied_methods = false:suggestion +csharp_style_expression_bodied_constructors = true:suggestion +csharp_style_expression_bodied_operators = true:suggestion +csharp_style_expression_bodied_properties = true:suggestion +csharp_style_expression_bodied_indexers = true:suggestion +csharp_style_expression_bodied_accessors = true:suggestion +csharp_style_expression_bodied_lambdas = true:suggestion +csharp_style_expression_bodied_local_functions = true:suggestion +# Pattern matching +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#pattern-matching +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +# Inlined variable declarations +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#inlined-variable-declarations +csharp_style_inlined_variable_declaration = true:warning +# Expression-level preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences +csharp_prefer_simple_default_expression = true:warning +# "Null" checking preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-null-checking-preferences +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:warning +# Code block preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#code-block-preferences +csharp_prefer_braces = true:warning +# Unused value preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#unused-value-preferences +csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +# Index and range preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#index-and-range-preferences +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning +# Miscellaneous preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#miscellaneous-preferences +csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +csharp_using_directive_placement = outside_namespace:warning +csharp_prefer_static_local_function = true:warning +csharp_prefer_simple_using_statement = true:suggestion + +########################################## +# .NET Formatting Conventions +# https://docs.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions +########################################## + +# Organize usings +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#organize-using-directives +dotnet_sort_system_directives_first = true +# Newline options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#new-line-options +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +# Indentation options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#indentation-options +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = no_change +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents_when_block = false +# Spacing options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#spacing-options +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_after_comma = true +csharp_space_before_comma = false +csharp_space_after_dot = false +csharp_space_before_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_before_semicolon_in_for_statement = false +csharp_space_around_declaration_statements = false +csharp_space_before_open_square_brackets = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_square_brackets = false +# Wrapping options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#wrap-options +csharp_preserve_single_line_statements = false +csharp_preserve_single_line_blocks = true + +########################################## +# .NET Naming Conventions +# https://docs.microsoft.com/visualstudio/ide/editorconfig-naming-conventions +########################################## + +[*.{cs,csx,cake,vb,vbx}] + +########################################## +# Styles +########################################## + +# camel_case_style - Define the camelCase style +dotnet_naming_style.camel_case_style.capitalization = camel_case +# pascal_case_style - Define the PascalCase style +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# first_upper_style - The first character must start with an upper-case character +dotnet_naming_style.first_upper_style.capitalization = first_word_upper +# prefix_interface_with_i_style - Interfaces must be PascalCase and the first character of an interface must be an 'I' +dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case +dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I +# prefix_type_parameters_with_t_style - Generic Type Parameters must be PascalCase and the first character must be a 'T' +dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case +dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T +# disallowed_style - Anything that has this style applied is marked as disallowed +dotnet_naming_style.disallowed_style.capitalization = pascal_case +dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____ +dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____ +# internal_error_style - This style should never occur... if it does, it indicates a bug in file or in the parser using the file +dotnet_naming_style.internal_error_style.capitalization = pascal_case +dotnet_naming_style.internal_error_style.required_prefix = ____INTERNAL_ERROR____ +dotnet_naming_style.internal_error_style.required_suffix = ____INTERNAL_ERROR____ + +########################################## +# .NET Design Guideline Field Naming Rules +# Naming rules for fields follow the .NET Framework design guidelines +# https://docs.microsoft.com/dotnet/standard/design-guidelines/index +########################################## + +# All public/protected/protected_internal constant fields must be PascalCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities = public, protected, protected_internal, internal, private +dotnet_naming_symbols.public_protected_constant_fields_group.required_modifiers = const +dotnet_naming_symbols.public_protected_constant_fields_group.applicable_kinds = field +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.symbols = public_protected_constant_fields_group +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.severity = warning + +# All public/protected/protected_internal static readonly fields must be PascalCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_accessibilities = public, protected, protected_internal +dotnet_naming_symbols.public_protected_static_readonly_fields_group.required_modifiers = static, readonly +dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_kinds = field +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.symbols = public_protected_static_readonly_fields_group +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity = warning + +# No other public/protected/protected_internal fields are allowed +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.other_public_protected_fields_group.applicable_accessibilities = public, protected, protected_internal +dotnet_naming_symbols.other_public_protected_fields_group.applicable_kinds = field +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols = other_public_protected_fields_group +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style = disallowed_style +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity = error + +# This rule should never fire. However, it's included for at least two purposes: +# First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers. +# Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#). +dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_accessibilities = * +dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_kinds = field +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols = sanity_check_uncovered_field_case_group +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style = internal_error_style +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity = error + +########################################## +# Other Naming Rules +########################################## + +# All of the following must be PascalCase: +# - Namespaces +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-namespaces +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md +# - Classes and Enumerations +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md +# - Delegates +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces#names-of-common-types +# - Constructors, Properties, Events, Methods +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-type-members +dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property +dotnet_naming_rule.element_rule.symbols = element_group +dotnet_naming_rule.element_rule.style = pascal_case_style +dotnet_naming_rule.element_rule.severity = warning + +# Interfaces use PascalCase and are prefixed with uppercase 'I' +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +dotnet_naming_symbols.interface_group.applicable_kinds = interface +dotnet_naming_rule.interface_rule.symbols = interface_group +dotnet_naming_rule.interface_rule.style = prefix_interface_with_i_style +dotnet_naming_rule.interface_rule.severity = warning + +# Generics Type Parameters use PascalCase and are prefixed with uppercase 'T' +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter +dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group +dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style +dotnet_naming_rule.type_parameter_rule.severity = warning + +# Function parameters use camelCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters +dotnet_naming_symbols.parameters_group.applicable_kinds = parameter +dotnet_naming_rule.parameters_rule.symbols = parameters_group +dotnet_naming_rule.parameters_rule.style = camel_case_style +dotnet_naming_rule.parameters_rule.severity = warning + +# Instance fields use camelCase and are prefixed with '_' +dotnet_naming_rule.instance_fields_should_be_camel_case.severity = warning +dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields +dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style +dotnet_naming_symbols.instance_fields.applicable_kinds = field +dotnet_naming_style.instance_field_style.capitalization = camel_case +dotnet_naming_style.instance_field_style.required_prefix = _ + +########################################## +# License +########################################## +# The following applies as to the .editorconfig file ONLY, and is +# included below for reference, per the requirements of the license +# corresponding to this .editorconfig file. +# See: https://github.com/RehanSaeed/EditorConfig +# +# MIT License +# +# Copyright (c) 2017-2019 Muhammad Rehan Saeed +# Copyright (c) 2019 Henry Gabryjelski +# +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, +# sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject +# to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +########################################## diff --git a/src/Sanitiser.TestSite.V10/.gitignore b/src/Sanitiser.TestSite.V10/.gitignore new file mode 100644 index 0000000..8df7125 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/.gitignore @@ -0,0 +1,481 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# JetBrains Rider +.idea/ +*.sln.iml + +## +## Visual Studio Code +## +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +## +## Umbraco CMS +## + +# JSON schema file for appsettings.json +appsettings-schema.json + +# Packages created from the backoffice (package.xml/package.zip) +/umbraco/Data/CreatedPackages/ + +# Temp folder containing Examine indexes, NuCache, MediaCache, etc. +/umbraco/Data/TEMP/ + +# SQLite database files - enable if you don't want to commit your test DB to the repo +/umbraco/Data/*.sqlite.db +/umbraco/Data/*.sqlite.db-shm +/umbraco/Data/*.sqlite.db-wal + +# Log files +/umbraco/Logs/ + +# Media files +/wwwroot/media/ + +# Test Site App_Plugins packages folder (exclude here as in Sanitiser project) +/App_Plugins/Sanitiser/ diff --git a/src/Sanitiser.TestSite.V10/Program.cs b/src/Sanitiser.TestSite.V10/Program.cs new file mode 100644 index 0000000..9222d42 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Program.cs @@ -0,0 +1,22 @@ +namespace Sanitiser.TestSite.v10; + +public class Program +{ + public static void Main(string[] args) + { + CreateHostBuilder(args) + .Build() + .Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureUmbracoDefaults() + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStaticWebAssets(); + webBuilder.UseStartup(); + }); + } +} diff --git a/src/Sanitiser.TestSite.V10/Properties/launchSettings.json b/src/Sanitiser.TestSite.V10/Properties/launchSettings.json new file mode 100644 index 0000000..f02d148 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:4665", + "sslPort": 44303 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Umbraco.Web.UI": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:44303;http://localhost:4665", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Sanitiser.TestSite.V10/Sanitiser.TestSite.v10.csproj b/src/Sanitiser.TestSite.V10/Sanitiser.TestSite.v10.csproj new file mode 100644 index 0000000..5116e94 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Sanitiser.TestSite.v10.csproj @@ -0,0 +1,27 @@ + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + true + + + + + + diff --git a/src/Sanitiser.TestSite.V10/Startup.cs b/src/Sanitiser.TestSite.V10/Startup.cs new file mode 100644 index 0000000..cea170e --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Startup.cs @@ -0,0 +1,64 @@ +namespace Sanitiser.TestSite.v10; + +public class Startup +{ + private readonly IConfiguration _config; + private readonly IWebHostEnvironment _env; + + /// + /// Initializes a new instance of the class. + /// + /// The web hosting environment. + /// The configuration. + /// + /// Only a few services are possible to be injected here https://github.com/dotnet/aspnetcore/issues/9337. + /// + public Startup(IWebHostEnvironment webHostEnvironment, IConfiguration config) + { + _env = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment)); + _config = config ?? throw new ArgumentNullException(nameof(config)); + } + + /// + /// Configures the services. + /// + /// The services. + /// + /// This method gets called by the runtime. Use this method to add services to the container. + /// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940. + /// + public void ConfigureServices(IServiceCollection services) + { + services.AddUmbraco(_env, _config) + .AddBackOffice() + .AddWebsite() + .AddComposers() + .Build(); + } + + /// + /// Configures the application. + /// + /// The application builder. + /// The web hosting environment. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseUmbraco() + .WithMiddleware(u => + { + u.UseBackOffice(); + u.UseWebsite(); + }) + .WithEndpoints(u => + { + u.UseInstallerEndpoints(); + u.UseBackOfficeEndpoints(); + u.UseWebsiteEndpoints(); + }); + } +} diff --git a/src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/area.cshtml b/src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/area.cshtml new file mode 100644 index 0000000..589dff6 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/area.cshtml @@ -0,0 +1,9 @@ +@inherits UmbracoViewPage + +
    + @await Html.GetBlockGridItemsHtmlAsync(Model) +
    diff --git a/src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/areas.cshtml b/src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/areas.cshtml new file mode 100644 index 0000000..c481f76 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/areas.cshtml @@ -0,0 +1,15 @@ +@inherits UmbracoViewPage +@{ + if (Model?.Areas.Any() != true) + { + return; + } +} + +
    + @foreach (var area in Model.Areas) + { + @await Html.GetBlockGridItemAreaHtmlAsync(area) + } +
    diff --git a/src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/default.cshtml b/src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/default.cshtml new file mode 100644 index 0000000..4b76f34 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/default.cshtml @@ -0,0 +1,13 @@ +@inherits UmbracoViewPage +@{ + if (Model?.Any() != true) + { + return; + } +} + +
    + @await Html.GetBlockGridItemsHtmlAsync(Model) +
    diff --git a/src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/items.cshtml b/src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/items.cshtml new file mode 100644 index 0000000..60498e9 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Views/Partials/blockgrid/items.cshtml @@ -0,0 +1,37 @@ +@inherits UmbracoViewPage> +@{ + if (Model?.Any() != true) + { + return; + } +} + +
    + @foreach (var item in Model) + { +
    + @{ + var partialViewName = "blockgrid/Components/" + item.Content.ContentType.Alias; + try + { + @await Html.PartialAsync(partialViewName, item) + } + catch (InvalidOperationException) + { +

    + Could not render component of type: @(item.Content.ContentType.Alias) +
    + This likely happened because the partial view @partialViewName could not be found. +

    + } + } +
    + } +
    diff --git a/src/Sanitiser.TestSite.V10/Views/Partials/blocklist/default.cshtml b/src/Sanitiser.TestSite.V10/Views/Partials/blocklist/default.cshtml new file mode 100644 index 0000000..cba3202 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Views/Partials/blocklist/default.cshtml @@ -0,0 +1,20 @@ +@inherits UmbracoViewPage +@{ + if (Model?.Any() != true) + { + return; + } +} +
    + @foreach (var block in Model) + { + if (block?.ContentUdi == null) + { + continue; + } + + var data = block.Content; + + @await Html.PartialAsync("blocklist/Components/" + data.ContentType.Alias, block) + } +
    diff --git a/src/Sanitiser.TestSite.V10/Views/Partials/grid/bootstrap3-fluid.cshtml b/src/Sanitiser.TestSite.V10/Views/Partials/grid/bootstrap3-fluid.cshtml new file mode 100644 index 0000000..79d34ec --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Views/Partials/grid/bootstrap3-fluid.cshtml @@ -0,0 +1,109 @@ +@using System.Collections +@using System.Web +@using Newtonsoft.Json.Linq +@inherits UmbracoViewPage + +@* + Razor helpers located at the bottom of this file +*@ + +@if (Model is JObject && Model?.sections is not null) +{ + var oneColumn = ((ICollection)Model.sections).Count == 1; + +
    + @if (oneColumn) + { + foreach (var section in Model.sections) + { +
    + @foreach (var row in section.rows) + { + renderRow(row); + } +
    + } + } + else + { +
    + @foreach (var sec in Model.sections) + { +
    +
    + @foreach (var row in sec.rows) + { + renderRow(row); + } +
    +
    + } +
    + } +
    +} + +@functions{ + + private async Task renderRow(dynamic row) + { +
    +
    + @foreach (var area in row.areas) + { +
    +
    + @foreach (var control in area.controls) + { + if (control?.editor?.view != null) + { + @await Html.PartialAsync("grid/editors/base", (object)control) + } + } +
    +
    + } +
    +
    + } + +} + +@functions{ + + public static HtmlString RenderElementAttributes(dynamic contentItem) + { + var attrs = new List(); + JObject cfg = contentItem.config; + + if (cfg != null) + { + foreach (var property in cfg.Properties()) + { + var propertyValue = HttpUtility.HtmlAttributeEncode(property.Value.ToString()); + attrs.Add(property.Name + "=\"" + propertyValue + "\""); + } + } + + JObject style = contentItem.styles; + + if (style != null) + { + var cssVals = new List(); + foreach (var property in style.Properties()) + { + var propertyValue = property.Value.ToString(); + if (string.IsNullOrWhiteSpace(propertyValue) == false) + { + cssVals.Add(property.Name + ":" + propertyValue + ";"); + } + } + + if (cssVals.Any()) + attrs.Add("style='" + HttpUtility.HtmlAttributeEncode(string.Join(" ", cssVals)) + "'"); + } + + return new HtmlString(string.Join(" ", attrs)); + } + +} diff --git a/src/Sanitiser.TestSite.V10/Views/Partials/grid/bootstrap3.cshtml b/src/Sanitiser.TestSite.V10/Views/Partials/grid/bootstrap3.cshtml new file mode 100644 index 0000000..ead2da5 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Views/Partials/grid/bootstrap3.cshtml @@ -0,0 +1,115 @@ +@using System.Collections +@using System.Web +@using Newtonsoft.Json.Linq +@inherits UmbracoViewPage + +@if (Model is JObject && Model?.sections is not null) +{ + var oneColumn = ((ICollection)Model.sections).Count == 1; + +
    + @if (oneColumn) + { + foreach (var section in Model.sections) + { +
    + @foreach (var row in section.rows) + { + renderRow(row, true); + } +
    + } + } + else + { +
    +
    + @foreach (var sec in Model.sections) + { +
    +
    + @foreach (var row in sec.rows) + { + renderRow(row, false); + } +
    +
    + } +
    +
    + } +
    +} + +@functions{ + + private async Task renderRow(dynamic row, bool singleColumn) + { +
    + @if (singleColumn) + { + @:
    + } +
    + @foreach (var area in row.areas) + { +
    +
    + @foreach (var control in area.controls) + { + if (control?.editor?.view != null) + { + @await Html.PartialAsync("grid/editors/base", (object)control) + } + } +
    +
    + } +
    + @if (singleColumn) + { + @:
    + } +
    + } + +} + +@functions{ + + public static HtmlString RenderElementAttributes(dynamic contentItem) + { + var attrs = new List(); + JObject cfg = contentItem.config; + + if (cfg != null) + { + foreach (var property in cfg.Properties()) + { + var propertyValue = HttpUtility.HtmlAttributeEncode(property.Value.ToString()); + attrs.Add(property.Name + "=\"" + propertyValue + "\""); + } + } + + JObject style = contentItem.styles; + + if (style != null) + { + var cssVals = new List(); + foreach (var property in style.Properties()) + { + var propertyValue = property.Value.ToString(); + if (string.IsNullOrWhiteSpace(propertyValue) == false) + { + cssVals.Add(property.Name + ":" + propertyValue + ";"); + } + } + + if (cssVals.Any()) + attrs.Add("style=\"" + HttpUtility.HtmlAttributeEncode(string.Join(" ", cssVals)) + "\""); + } + + return new HtmlString(string.Join(" ", attrs)); + } + +} diff --git a/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/base.cshtml b/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/base.cshtml new file mode 100644 index 0000000..3c9d421 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/base.cshtml @@ -0,0 +1,28 @@ +@model dynamic + +@try +{ + string editor = EditorView(Model); + @await Html.PartialAsync(editor, Model as object) +} +catch (Exception ex) +{ +
    @ex.ToString()
    +} + +@functions{ + + public static string EditorView(dynamic contentItem) + { + string view = contentItem.editor.render != null ? contentItem.editor.render.ToString() : contentItem.editor.view.ToString(); + view = view.Replace(".html", ".cshtml"); + + if (!view.Contains("/")) + { + view = "grid/editors/" + view; + } + + return view; + } + +} diff --git a/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/embed.cshtml b/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/embed.cshtml new file mode 100644 index 0000000..305ea07 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/embed.cshtml @@ -0,0 +1,11 @@ +@inherits UmbracoViewPage + +@if (Model is not null) +{ + string embedValue = Convert.ToString(Model.value); + embedValue = embedValue.DetectIsJson() ? Model.value.preview : Model.value; + +
    + @Html.Raw(embedValue) +
    +} diff --git a/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/macro.cshtml b/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/macro.cshtml new file mode 100644 index 0000000..1aaed84 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/macro.cshtml @@ -0,0 +1,15 @@ +@inherits UmbracoViewPage + +@if (Model?.value is not null) +{ + string macroAlias = Model.value.macroAlias.ToString(); + var parameters = new Dictionary(); + foreach (var mpd in Model.value.macroParamsDictionary) + { + parameters.Add(mpd.Name, mpd.Value); + } + + + @await Umbraco.RenderMacroAsync(macroAlias, parameters) + +} diff --git a/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/media.cshtml b/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/media.cshtml new file mode 100644 index 0000000..1b6e9f0 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/media.cshtml @@ -0,0 +1,60 @@ +@using Umbraco.Cms.Core.Media +@using Umbraco.Cms.Core.PropertyEditors.ValueConverters +@model dynamic +@inject IImageUrlGenerator ImageUrlGenerator + +@if (Model?.value is not null) +{ + var url = Model.value.image; + + if (Model.editor.config != null && Model.editor.config.size != null) + { + if (Model.value.coordinates != null) + { + url = ((string)url).GetCropUrl(ImageUrlGenerator, + width: (int)Model.editor.config.size.width, + height: (int)Model.editor.config.size.height, + cropAlias: "default", + cropDataSet: new ImageCropperValue + { + Crops = new[] + { + new ImageCropperValue.ImageCropperCrop + { + Alias = "default", + Coordinates = new ImageCropperValue.ImageCropperCropCoordinates + { + X1 = (decimal)Model.value.coordinates.x1, + Y1 = (decimal)Model.value.coordinates.y1, + X2 = (decimal)Model.value.coordinates.x2, + Y2 = (decimal)Model.value.coordinates.y2 + } + } + } + }); + } + else + { + url = ((string)url).GetCropUrl(ImageUrlGenerator, + width: (int)Model.editor.config.size.width, + height: (int)Model.editor.config.size.height, + cropDataSet: new ImageCropperValue + { + FocalPoint = new ImageCropperValue.ImageCropperFocalPoint + { + Top = Model.value.focalPoint == null ? 0.5m : Model.value.focalPoint.top, + Left = Model.value.focalPoint == null ? 0.5m : Model.value.focalPoint.left + } + }); + } + } + + var altText = Model.value.altText ?? Model.value.caption ?? string.Empty; + + @altText + + if (Model.value.caption != null) + { +

    @Model.value.caption

    + } +} diff --git a/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/rte.cshtml b/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/rte.cshtml new file mode 100644 index 0000000..c20e85e --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/rte.cshtml @@ -0,0 +1,13 @@ +@using Umbraco.Cms.Core.Templates +@model dynamic +@inject HtmlLocalLinkParser HtmlLocalLinkParser; +@inject HtmlUrlParser HtmlUrlParser; +@inject HtmlImageSourceParser HtmlImageSourceParser; + +@{ + var value = HtmlLocalLinkParser.EnsureInternalLinks(Model?.value.ToString()); + value = HtmlUrlParser.EnsureUrls(value); + value = HtmlImageSourceParser.EnsureImageSources(value); +} + +@Html.Raw(value) diff --git a/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/textstring.cshtml b/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/textstring.cshtml new file mode 100644 index 0000000..665a8cb --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Views/Partials/grid/editors/textstring.cshtml @@ -0,0 +1,22 @@ +@model dynamic + +@if (Model?.editor.config.markup is not null) +{ + string markup = Model.editor.config.markup.ToString(); + markup = markup.Replace("#value#", Html.ReplaceLineBreaks((string)Model.value.ToString()).ToString()); + + if (Model.editor.config.style != null) + { + markup = markup.Replace("#style#", Model.editor.config.style.ToString()); + } + + + @Html.Raw(markup) + +} +else +{ + +
    @Model?.value
    +
    +} diff --git a/src/Sanitiser.TestSite.V10/Views/_ViewImports.cshtml b/src/Sanitiser.TestSite.V10/Views/_ViewImports.cshtml new file mode 100644 index 0000000..45fb9f0 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/Views/_ViewImports.cshtml @@ -0,0 +1,10 @@ +@using Umbraco.Extensions +@using Sanitiser.TestSite.v10 +@using Umbraco.Cms.Web.Common.PublishedModels +@using Umbraco.Cms.Web.Common.Views +@using Umbraco.Cms.Core.Models.PublishedContent +@using Microsoft.AspNetCore.Html +@using Smidge +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Smidge +@inject SmidgeHelper SmidgeHelper diff --git a/src/Sanitiser.TestSite.V10/appsettings.Development.json b/src/Sanitiser.TestSite.V10/appsettings.Development.json new file mode 100644 index 0000000..faeb73c --- /dev/null +++ b/src/Sanitiser.TestSite.V10/appsettings.Development.json @@ -0,0 +1,34 @@ +{ + "$schema": "./appsettings-schema.json", + "Serilog": { + "MinimumLevel": { + "Default": "Information" + }, + "WriteTo": [ + { + "Name": "Async", + "Args": { + "configure": [ + { + "Name": "Console" + } + ] + } + } + ] + }, + "Umbraco": { + "CMS": { + "Content": { + "MacroErrors": "Throw" + }, + "Hosting": { + "Debug": true + }, + "RuntimeMinification": { + "UseInMemoryCache": true, + "CacheBuster": "Timestamp" + } + } + } +} diff --git a/src/Sanitiser.TestSite.V10/appsettings.json b/src/Sanitiser.TestSite.V10/appsettings.json new file mode 100644 index 0000000..22c93e0 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/appsettings.json @@ -0,0 +1,50 @@ +{ + "$schema": "./appsettings-schema.json", + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "System": "Warning" + } + } + }, + "Umbraco": { + "CMS": { + "Global": { + "Id": "fa426183-f13d-4c6d-926e-5290e53bce1d", + "SanitizeTinyMce": true + }, + "Content": { + "AllowEditInvariantFromNonDefault": true, + "ContentVersionCleanupPolicy": { + "EnableCleanup": true + } + }, + "ModelsBuilder": { + "ModelsMode": "SourceCodeManual" + }, + "Unattended": { + "InstallUnattended": true, + "UnattendedUserName": "Admin", + "UnattendedUserEmail": "admin@example.com", + "UnattendedUserPassword": "lA4&DyZjnpp" + } + } + }, + "ConnectionStrings": { + "umbracoDbDSN": "Data Source=|DataDirectory|/Umbraco.sqlite.db;Cache=Shared;Foreign Keys=True;Pooling=True", + "umbracoDbDSN_ProviderName": "Microsoft.Data.Sqlite" + }, + "Sanitiser": { + "Enable": true, + "UmbracoFormsSanitiser": { + "Enable": true + }, + "MembersSanitiser": { + "Enable": true, + "DomainsToExclude": "test.com,example.com" + } + } +} diff --git a/src/Sanitiser.TestSite.V10/umbraco/Licenses/umbracoForms.lic b/src/Sanitiser.TestSite.V10/umbraco/Licenses/umbracoForms.lic new file mode 100644 index 0000000..e0f1e6b --- /dev/null +++ b/src/Sanitiser.TestSite.V10/umbraco/Licenses/umbracoForms.lic @@ -0,0 +1 @@ +76E6-AE74-5179-58AE-2328-901F-475B-9C02-BE96-49A6-65AA-C463-35EE-45E7-2A64-05BF-6824-E5C6-D977-6A35-F78F-6E81-A79B-A5C4-AB0E-B701-DE4C-C12E-F1B9-3A0E-8D5D-B6C8-AFC8-0690-82B3-D187-1636-BB82-CBE7-9308-55E4-534D-39DF-FC33-340B-76A7-DCF1-7B15-D022-69D1-BF30-C36D-53CE-B191-C4BA-C0F2-F24B-5EFB-286E-7DC2-2CF9-3762-32EE-E474-BDF1-D08B-AA4F-CDC7-DC7B-DB15-ACCE-2CF9-02D7-5FDE-DC5E-494B-A34A-987E-28BB-BC07-1043-66D7-A149-6BFF-AB42-4F07-8A99-796B-9161-FF18-5365-5206-7D3F-F80A-4636-0C88-DF2B-1A61-BFC3-0E75-FA07-ABB1-C1F5-4CE9-1B91-8297-40B6-9016-518B-C982-2569-4C86-809E-C652-E188-6D8E-FC3C-152C-A9ED-159F-044A-F865-FA95-979D-6C03-AE4E-BD40-947A-F801-3FF0-F205-3081-2FE3-8E6E-52F2-5FA7-7BFC-C369-19AF-EF19-BC0E-7DDA-DEAB-163B-0A59-ACAA-09ED-7FD6-96B7-DCF8-B894-101A-23C5-81A0-36EA-F0AA-A2D5-4046-52F0-82FC-9B7A-69F6-2F5B-50EB-EB4E-8417-7E6D-56B6-6727-4831-9D36-8CCC-1288-81AE-A4FB-3E5B-DFA8-D39F-31F3-4C5C-027C-5DC0-474F-2EC8-E92D-0345-9C55-7C5A-98C4-0FA3-2C6D-187E-211E-D6FA-0B02-A3F9-6E09-A0C5-422D-559B-B7ED-55D0-C127-A623-7C1F-A5B5-8442-0185-B79E-6BCE-A313-880E-6AF0-1F97-9AA1-2367-A529-D8CB-9E9E-520E-FB54-E68A-705B-21AF diff --git a/src/Sanitiser.TestSite.V10/umbraco/models/File.generated.cs b/src/Sanitiser.TestSite.V10/umbraco/models/File.generated.cs new file mode 100644 index 0000000..2f851ac --- /dev/null +++ b/src/Sanitiser.TestSite.V10/umbraco/models/File.generated.cs @@ -0,0 +1,75 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder.Embedded v10.8.5+7e1d1a1 +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Linq.Expressions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +using Umbraco.Cms.Core; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.PublishedModels +{ + /// File + [PublishedModel("File")] + public partial class File : PublishedContentModel + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const string ModelTypeAlias = "File"; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const PublishedItemType ModelItemType = PublishedItemType.Media; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public static IPublishedPropertyType GetModelPropertyType(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector); +#pragma warning restore 0109 + + private IPublishedValueFallback _publishedValueFallback; + + // ctor + public File(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + + /// + /// Size: in bytes + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [ImplementPropertyType("umbracoBytes")] + public virtual long UmbracoBytes => this.Value(_publishedValueFallback, "umbracoBytes"); + + /// + /// Type + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType("umbracoExtension")] + public virtual string UmbracoExtension => this.Value(_publishedValueFallback, "umbracoExtension"); + + /// + /// File + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType("umbracoFile")] + public virtual string UmbracoFile => this.Value(_publishedValueFallback, "umbracoFile"); + } +} diff --git a/src/Sanitiser.TestSite.V10/umbraco/models/Folder.generated.cs b/src/Sanitiser.TestSite.V10/umbraco/models/Folder.generated.cs new file mode 100644 index 0000000..894ef4e --- /dev/null +++ b/src/Sanitiser.TestSite.V10/umbraco/models/Folder.generated.cs @@ -0,0 +1,52 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder.Embedded v10.8.5+7e1d1a1 +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Linq.Expressions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +using Umbraco.Cms.Core; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.PublishedModels +{ + /// Folder + [PublishedModel("Folder")] + public partial class Folder : PublishedContentModel + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const string ModelTypeAlias = "Folder"; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const PublishedItemType ModelItemType = PublishedItemType.Media; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public static IPublishedPropertyType GetModelPropertyType(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector); +#pragma warning restore 0109 + + private IPublishedValueFallback _publishedValueFallback; + + // ctor + public Folder(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + } +} diff --git a/src/Sanitiser.TestSite.V10/umbraco/models/Image.generated.cs b/src/Sanitiser.TestSite.V10/umbraco/models/Image.generated.cs new file mode 100644 index 0000000..c7f09d6 --- /dev/null +++ b/src/Sanitiser.TestSite.V10/umbraco/models/Image.generated.cs @@ -0,0 +1,89 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder.Embedded v10.8.5+7e1d1a1 +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Linq.Expressions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +using Umbraco.Cms.Core; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.PublishedModels +{ + /// Image + [PublishedModel("Image")] + public partial class Image : PublishedContentModel + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const string ModelTypeAlias = "Image"; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const PublishedItemType ModelItemType = PublishedItemType.Media; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public static IPublishedPropertyType GetModelPropertyType(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector); +#pragma warning restore 0109 + + private IPublishedValueFallback _publishedValueFallback; + + // ctor + public Image(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + + /// + /// Size: in bytes + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [ImplementPropertyType("umbracoBytes")] + public virtual long UmbracoBytes => this.Value(_publishedValueFallback, "umbracoBytes"); + + /// + /// Type + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType("umbracoExtension")] + public virtual string UmbracoExtension => this.Value(_publishedValueFallback, "umbracoExtension"); + + /// + /// Image + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType("umbracoFile")] + public virtual global::Umbraco.Cms.Core.PropertyEditors.ValueConverters.ImageCropperValue UmbracoFile => this.Value(_publishedValueFallback, "umbracoFile"); + + /// + /// Height: in pixels + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [ImplementPropertyType("umbracoHeight")] + public virtual int UmbracoHeight => this.Value(_publishedValueFallback, "umbracoHeight"); + + /// + /// Width: in pixels + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [ImplementPropertyType("umbracoWidth")] + public virtual int UmbracoWidth => this.Value(_publishedValueFallback, "umbracoWidth"); + } +} diff --git a/src/Sanitiser.TestSite.V10/umbraco/models/Member.generated.cs b/src/Sanitiser.TestSite.V10/umbraco/models/Member.generated.cs new file mode 100644 index 0000000..591972d --- /dev/null +++ b/src/Sanitiser.TestSite.V10/umbraco/models/Member.generated.cs @@ -0,0 +1,60 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder.Embedded v10.8.5+7e1d1a1 +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Linq.Expressions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +using Umbraco.Cms.Core; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.PublishedModels +{ + /// Member + [PublishedModel("Member")] + public partial class Member : PublishedContentModel + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const string ModelTypeAlias = "Member"; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const PublishedItemType ModelItemType = PublishedItemType.Member; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public static IPublishedPropertyType GetModelPropertyType(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector); +#pragma warning restore 0109 + + private IPublishedValueFallback _publishedValueFallback; + + // ctor + public Member(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + + /// + /// Comments + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType("umbracoMemberComments")] + public virtual string UmbracoMemberComments => this.Value(_publishedValueFallback, "umbracoMemberComments"); + } +} diff --git a/src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaArticle.generated.cs b/src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaArticle.generated.cs new file mode 100644 index 0000000..63901bd --- /dev/null +++ b/src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaArticle.generated.cs @@ -0,0 +1,75 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder.Embedded v10.8.5+7e1d1a1 +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Linq.Expressions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +using Umbraco.Cms.Core; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.PublishedModels +{ + /// Article + [PublishedModel("umbracoMediaArticle")] + public partial class UmbracoMediaArticle : PublishedContentModel + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const string ModelTypeAlias = "umbracoMediaArticle"; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const PublishedItemType ModelItemType = PublishedItemType.Media; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public static IPublishedPropertyType GetModelPropertyType(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector); +#pragma warning restore 0109 + + private IPublishedValueFallback _publishedValueFallback; + + // ctor + public UmbracoMediaArticle(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + + /// + /// Size: in bytes + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [ImplementPropertyType("umbracoBytes")] + public virtual long UmbracoBytes => this.Value(_publishedValueFallback, "umbracoBytes"); + + /// + /// Type + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType("umbracoExtension")] + public virtual string UmbracoExtension => this.Value(_publishedValueFallback, "umbracoExtension"); + + /// + /// Article + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType("umbracoFile")] + public virtual string UmbracoFile => this.Value(_publishedValueFallback, "umbracoFile"); + } +} diff --git a/src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaAudio.generated.cs b/src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaAudio.generated.cs new file mode 100644 index 0000000..4f10fea --- /dev/null +++ b/src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaAudio.generated.cs @@ -0,0 +1,75 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder.Embedded v10.8.5+7e1d1a1 +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Linq.Expressions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +using Umbraco.Cms.Core; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.PublishedModels +{ + /// Audio + [PublishedModel("umbracoMediaAudio")] + public partial class UmbracoMediaAudio : PublishedContentModel + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const string ModelTypeAlias = "umbracoMediaAudio"; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const PublishedItemType ModelItemType = PublishedItemType.Media; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public static IPublishedPropertyType GetModelPropertyType(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector); +#pragma warning restore 0109 + + private IPublishedValueFallback _publishedValueFallback; + + // ctor + public UmbracoMediaAudio(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + + /// + /// Size: in bytes + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [ImplementPropertyType("umbracoBytes")] + public virtual long UmbracoBytes => this.Value(_publishedValueFallback, "umbracoBytes"); + + /// + /// Type + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType("umbracoExtension")] + public virtual string UmbracoExtension => this.Value(_publishedValueFallback, "umbracoExtension"); + + /// + /// Audio + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType("umbracoFile")] + public virtual string UmbracoFile => this.Value(_publishedValueFallback, "umbracoFile"); + } +} diff --git a/src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaVectorGraphics.generated.cs b/src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaVectorGraphics.generated.cs new file mode 100644 index 0000000..cb203da --- /dev/null +++ b/src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaVectorGraphics.generated.cs @@ -0,0 +1,75 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder.Embedded v10.8.5+7e1d1a1 +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Linq.Expressions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +using Umbraco.Cms.Core; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.PublishedModels +{ + /// Vector Graphics (SVG) + [PublishedModel("umbracoMediaVectorGraphics")] + public partial class UmbracoMediaVectorGraphics : PublishedContentModel + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const string ModelTypeAlias = "umbracoMediaVectorGraphics"; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const PublishedItemType ModelItemType = PublishedItemType.Media; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public static IPublishedPropertyType GetModelPropertyType(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector); +#pragma warning restore 0109 + + private IPublishedValueFallback _publishedValueFallback; + + // ctor + public UmbracoMediaVectorGraphics(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + + /// + /// Size: in bytes + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [ImplementPropertyType("umbracoBytes")] + public virtual long UmbracoBytes => this.Value(_publishedValueFallback, "umbracoBytes"); + + /// + /// Type + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType("umbracoExtension")] + public virtual string UmbracoExtension => this.Value(_publishedValueFallback, "umbracoExtension"); + + /// + /// Vector Graphics + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType("umbracoFile")] + public virtual string UmbracoFile => this.Value(_publishedValueFallback, "umbracoFile"); + } +} diff --git a/src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaVideo.generated.cs b/src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaVideo.generated.cs new file mode 100644 index 0000000..b8a092a --- /dev/null +++ b/src/Sanitiser.TestSite.V10/umbraco/models/UmbracoMediaVideo.generated.cs @@ -0,0 +1,75 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder.Embedded v10.8.5+7e1d1a1 +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Linq.Expressions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +using Umbraco.Cms.Core; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.PublishedModels +{ + /// Video + [PublishedModel("umbracoMediaVideo")] + public partial class UmbracoMediaVideo : PublishedContentModel + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const string ModelTypeAlias = "umbracoMediaVideo"; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + public new const PublishedItemType ModelItemType = PublishedItemType.Media; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [return: global::System.Diagnostics.CodeAnalysis.MaybeNull] + public static IPublishedPropertyType GetModelPropertyType(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector); +#pragma warning restore 0109 + + private IPublishedValueFallback _publishedValueFallback; + + // ctor + public UmbracoMediaVideo(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + + /// + /// Size: in bytes + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [ImplementPropertyType("umbracoBytes")] + public virtual long UmbracoBytes => this.Value(_publishedValueFallback, "umbracoBytes"); + + /// + /// Type + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType("umbracoExtension")] + public virtual string UmbracoExtension => this.Value(_publishedValueFallback, "umbracoExtension"); + + /// + /// Video + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "10.8.5+7e1d1a1")] + [global::System.Diagnostics.CodeAnalysis.MaybeNull] + [ImplementPropertyType("umbracoFile")] + public virtual string UmbracoFile => this.Value(_publishedValueFallback, "umbracoFile"); + } +} diff --git a/src/Sanitiser.TestSite.V10/wwwroot/favicon.ico b/src/Sanitiser.TestSite.V10/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c0749ddf7f1b68606b0672aa8d709779d6b0817a GIT binary patch literal 15406 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 literal 0 HcmV?d00001 diff --git a/src/Sanitiser.sln b/src/Sanitiser.sln new file mode 100644 index 0000000..83c1139 --- /dev/null +++ b/src/Sanitiser.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33020.496 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8F296AE8-9B50-47BE-B6FE-8B78A0615219}" + ProjectSection(SolutionItems) = preProject + ..\LICENSE = ..\LICENSE + ..\.github\README.md = ..\.github\README.md + ..\umbraco-marketplace.json = ..\umbraco-marketplace.json + ..\.editorconfig = ..\.editorconfig + ..\.gitignore = ..\.gitignore + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sanitiser", "Sanitiser\Sanitiser.csproj", "{E967E3B0-AF08-49D8-AB0B-21BD054686CF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sanitiser.TestSite.v10", "Sanitiser.TestSite.V10\Sanitiser.TestSite.v10.csproj", "{C43328ED-8B6E-485B-A9ED-AE0CF750D7AD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E967E3B0-AF08-49D8-AB0B-21BD054686CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E967E3B0-AF08-49D8-AB0B-21BD054686CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E967E3B0-AF08-49D8-AB0B-21BD054686CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E967E3B0-AF08-49D8-AB0B-21BD054686CF}.Release|Any CPU.Build.0 = Release|Any CPU + {C43328ED-8B6E-485B-A9ED-AE0CF750D7AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C43328ED-8B6E-485B-A9ED-AE0CF750D7AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C43328ED-8B6E-485B-A9ED-AE0CF750D7AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C43328ED-8B6E-485B-A9ED-AE0CF750D7AD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6F07004F-9903-4AEB-9ED0-BE57A0326D20} + EndGlobalSection +EndGlobal diff --git a/src/Sanitiser/Collections/SanitisersCollection.cs b/src/Sanitiser/Collections/SanitisersCollection.cs new file mode 100644 index 0000000..487248e --- /dev/null +++ b/src/Sanitiser/Collections/SanitisersCollection.cs @@ -0,0 +1,6 @@ +using Umbraco.Cms.Core.Composing; +using Umbraco.Community.Sanitiser.sanitisers; + +namespace Umbraco.Community.Sanitiser.collections; + +public class SanitisersCollection(Func> items) : BuilderCollectionBase(items); diff --git a/src/Sanitiser/Collections/SanitisersCollectionBuilder.cs b/src/Sanitiser/Collections/SanitisersCollectionBuilder.cs new file mode 100644 index 0000000..d66e67e --- /dev/null +++ b/src/Sanitiser/Collections/SanitisersCollectionBuilder.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Composing; +using Umbraco.Community.Sanitiser.sanitisers; + +namespace Umbraco.Community.Sanitiser.collections; + +public class + SanitisersCollectionBuilder : LazyCollectionBuilderBase +{ + protected override SanitisersCollectionBuilder This => this; +} diff --git a/src/Sanitiser/Configuration/MembersSanitiserOptions.cs b/src/Sanitiser/Configuration/MembersSanitiserOptions.cs new file mode 100644 index 0000000..14174be --- /dev/null +++ b/src/Sanitiser/Configuration/MembersSanitiserOptions.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Community.Sanitiser.Configuration; + +public class MembersSanitiserOptions +{ + public bool Enable { get; init; } + + public string DomainsToExclude { get; init; } = string.Empty; +} diff --git a/src/Sanitiser/Configuration/SanitiserOptions.cs b/src/Sanitiser/Configuration/SanitiserOptions.cs new file mode 100644 index 0000000..bb6a099 --- /dev/null +++ b/src/Sanitiser/Configuration/SanitiserOptions.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Community.Sanitiser.Configuration; + +public class SanitiserOptions +{ + public const string SanitiserOptionsKey = "Sanitiser"; + public bool Enable { get; init; } + public UmbracoFormsSanitisiationOptions? UmbracoFormsSanitiser { get; init; } + public MembersSanitiserOptions? MembersSanitiser { get; init; } +} diff --git a/src/Sanitiser/Configuration/UmbracoFormsSanitisiationOptions.cs b/src/Sanitiser/Configuration/UmbracoFormsSanitisiationOptions.cs new file mode 100644 index 0000000..a27257e --- /dev/null +++ b/src/Sanitiser/Configuration/UmbracoFormsSanitisiationOptions.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Community.Sanitiser.Configuration; + +public class UmbracoFormsSanitisiationOptions +{ + public bool Enable { get; init; } +} diff --git a/src/Sanitiser/Models/UmbracoCacheInstruction.cs b/src/Sanitiser/Models/UmbracoCacheInstruction.cs new file mode 100644 index 0000000..37320af --- /dev/null +++ b/src/Sanitiser/Models/UmbracoCacheInstruction.cs @@ -0,0 +1,9 @@ +using NPoco; + +namespace Umbraco.Community.Sanitiser.Models; + +[TableName("umbracoCacheInstruction")] +public class UmbracoCacheInstruction +{ + [Column("jsonInstruction")] public string JsonInstruction { get; set; } = string.Empty; +} diff --git a/src/Sanitiser/Sanitiser.csproj b/src/Sanitiser/Sanitiser.csproj new file mode 100644 index 0000000..cbae870 --- /dev/null +++ b/src/Sanitiser/Sanitiser.csproj @@ -0,0 +1,43 @@ + + + net8.0 + enable + enable + App_Plugins + . + Umbraco.Community.Sanitiser + Umbraco.Community.Sanitiser + Sanitiser + ... + umbraco;umbraco-marketplace + Umbraco.Community.Sanitiser + True + 0.1.0 + Richard Thompson + $([System.DateTime]::UtcNow.ToString(`yyyy`)) © Richard Thompson + https://github.com/richarth/sanitiser + https://github.com/richarth/sanitiser + README_nuget.md + git + MIT + logo.png + + + + + + + + + + + True + \ + + + True + \ + + + + diff --git a/src/Sanitiser/SanitiserComposer.cs b/src/Sanitiser/SanitiserComposer.cs new file mode 100644 index 0000000..b3b6d23 --- /dev/null +++ b/src/Sanitiser/SanitiserComposer.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Community.Sanitiser.Configuration; +using Umbraco.Community.Sanitiser.sanitisers; +using Umbraco.Community.Sanitiser.services; + +namespace Umbraco.Community.Sanitiser; + +internal class SanitiserComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.ManifestFilters().Append(); + + builder.Sanitisers().Add(() => builder.TypeLoader.GetTypes()); + + builder.Services.AddSingleton(); + + builder.AddNotificationHandler(); + + builder.Services.Configure(builder.Config.GetSection(SanitiserOptions.SanitiserOptionsKey)); + } +} diff --git a/src/Sanitiser/SanitiserManifestFilter.cs b/src/Sanitiser/SanitiserManifestFilter.cs new file mode 100644 index 0000000..c1cc926 --- /dev/null +++ b/src/Sanitiser/SanitiserManifestFilter.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using Umbraco.Cms.Core.Manifest; + +namespace Umbraco.Community.Sanitiser; + +internal class SanitiserManifestFilter : IManifestFilter +{ + public void Filter(List manifests) + { + Assembly assembly = typeof(SanitiserManifestFilter).Assembly; + + manifests.Add(new PackageManifest + { + PackageName = "Sanitiser", + Version = assembly.GetName()?.Version?.ToString(3) ?? "0.1.0", + AllowPackageTelemetry = false + }); + } +} diff --git a/src/Sanitiser/Sanitisers/DatabaseTableSanitiser.cs b/src/Sanitiser/Sanitisers/DatabaseTableSanitiser.cs new file mode 100644 index 0000000..d9db278 --- /dev/null +++ b/src/Sanitiser/Sanitisers/DatabaseTableSanitiser.cs @@ -0,0 +1,44 @@ +using NPoco; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Scoping; + +namespace Umbraco.Community.Sanitiser.sanitisers; + +public abstract class DatabaseTableSanitiser(IScopeProvider scopeProvider) : ISanitiser +{ + public async Task Sanitise() + { + await EmptyTable(); + } + + public abstract bool IsEnabled(); + + private async Task EmptyTable() + { + using IScope scope = scopeProvider.CreateScope(); + + if (TableExists(scope.Database)) + { + await scope.Database.DeleteManyAsync().Execute(); + } + + scope.Complete(); + } + + private static bool TableExists(IUmbracoDatabase umbracoDatabase) + { + var tableName = GetTableName(); + + // check if the table exists in the database + return umbracoDatabase.SqlContext.SqlSyntax.DoesTableExist(umbracoDatabase, tableName); + } + + private static string GetTableName() + { + // find the name of the table being emptied + TableNameAttribute? tableNameAttribute = + (TableNameAttribute?)Attribute.GetCustomAttribute(typeof(T), typeof(TableNameAttribute)); + + return tableNameAttribute?.Value ?? string.Empty; + } +} diff --git a/src/Sanitiser/Sanitisers/ISanitiser.cs b/src/Sanitiser/Sanitisers/ISanitiser.cs new file mode 100644 index 0000000..6d95617 --- /dev/null +++ b/src/Sanitiser/Sanitisers/ISanitiser.cs @@ -0,0 +1,10 @@ +using Umbraco.Cms.Core.Composing; + +namespace Umbraco.Community.Sanitiser.sanitisers; + +public interface ISanitiser : IDiscoverable +{ + public Task Sanitise(); + + public bool IsEnabled(); +} diff --git a/src/Sanitiser/Sanitisers/MembersSanitiser.cs b/src/Sanitiser/Sanitisers/MembersSanitiser.cs new file mode 100644 index 0000000..fc10f3d --- /dev/null +++ b/src/Sanitiser/Sanitisers/MembersSanitiser.cs @@ -0,0 +1,70 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Scoping; +using Umbraco.Community.Sanitiser.Configuration; +using Umbraco.Community.Sanitiser.Models; + +namespace Umbraco.Community.Sanitiser.sanitisers; + +public class MembersSanitiser( + IOptions sanitiserOptions, + IMemberService memberService, + IScopeProvider scopeProvider) : ISanitiser +{ + private readonly SanitiserOptions _sanitiserOptions = sanitiserOptions.Value; + + public async Task Sanitise() + { + // remove all members then remove their cached data + await Task.WhenAll(RemoveAllMembers(), RemoveCachedMemberData()); + } + + public bool IsEnabled() + { + return _sanitiserOptions.MembersSanitiser?.Enable ?? false; + } + + private static bool IsEmailDomainExcluded(string email, string domainsToExclude) + { + return domainsToExclude.Contains(email.Split('@')[1]); + } + + private Task RemoveAllMembers() + { + var domainsToExclude = _sanitiserOptions.MembersSanitiser?.DomainsToExclude ?? string.Empty; + + memberService.GetAll(0, 10, out var numberOfMembers); + + for (var i = 0; i < numberOfMembers; i += 10) + { + IEnumerable members = memberService.GetAll(i, 10, out _); + + foreach (IMember member in members) + { + if (IsEmailDomainExcluded(member.Email, domainsToExclude)) + { + continue; + } + + memberService.Delete(member); + } + } + + return Task.CompletedTask; + } + + private async Task RemoveCachedMemberData() + { + using IScope scope = scopeProvider.CreateScope(); + + // the umbracoCacheInstruction table stores the member username, so we need to remove it + await scope.Database.DeleteMany().Where(x => + x.JsonInstruction.Contains( + $"\"RefresherId\":\"{MemberCacheRefresher.UniqueId.ToString().ToLowerInvariant()}\"")) + .ExecuteAsync(); + + scope.Complete(); + } +} diff --git a/src/Sanitiser/Sanitisers/UmbracoFormsSanitiser.cs b/src/Sanitiser/Sanitisers/UmbracoFormsSanitiser.cs new file mode 100644 index 0000000..a0f6f2b --- /dev/null +++ b/src/Sanitiser/Sanitisers/UmbracoFormsSanitiser.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Web; +using Umbraco.Community.Sanitiser.Configuration; +using Umbraco.Forms.Core.Models; +using Umbraco.Forms.Core.Persistence.Dtos; +using Umbraco.Forms.Core.Services; + +namespace Umbraco.Community.Sanitiser.sanitisers; + +public class UmbracoFormsSanitiser( + IOptions options, + IFormService formService, + IRecordService recordService, + IUmbracoContextFactory umbracoContextFactory) +{ + private readonly SanitiserOptions _options = options.Value; + + public Task Sanitise() + { + umbracoContextFactory.EnsureUmbracoContext(); + + IEnumerable
    forms = GetFormsOnSite(); + + foreach (Form form in forms) + { + IEnumerable formRecords = GetRecordsForForm(form); + + foreach (Record record in formRecords) + { + recordService.Delete(record, form); + } + } + + return Task.CompletedTask; + } + + public bool IsEnabled() + { + return _options.UmbracoFormsSanitiser?.Enable ?? false; + } + + private IEnumerable GetFormsOnSite() + { + return formService.Get(); + } + + private IEnumerable GetRecordsForForm(Form form) + { + return recordService.GetAllRecords(form, false); + } +} diff --git a/src/Sanitiser/SanitizationStartupNotification.cs b/src/Sanitiser/SanitizationStartupNotification.cs new file mode 100644 index 0000000..911c3d5 --- /dev/null +++ b/src/Sanitiser/SanitizationStartupNotification.cs @@ -0,0 +1,15 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Community.Sanitiser.collections; +using Umbraco.Community.Sanitiser.services; + +namespace Umbraco.Community.Sanitiser; + +public class SanitizationStartupNotification(ISanitisationService sanitizationService, SanitisersCollection sanitisers) + : INotificationHandler +{ + public void Handle(UmbracoApplicationStartingNotification notification) + { + sanitizationService.Sanitise(sanitisers); + } +} diff --git a/src/Sanitiser/Services/ISanitisationService.cs b/src/Sanitiser/Services/ISanitisationService.cs new file mode 100644 index 0000000..90580bf --- /dev/null +++ b/src/Sanitiser/Services/ISanitisationService.cs @@ -0,0 +1,10 @@ +using Umbraco.Community.Sanitiser.collections; + +namespace Umbraco.Community.Sanitiser.services; + +public interface ISanitisationService +{ + public Task Sanitise(SanitisersCollection sanitisers); + + public bool IsEnabled(); +} diff --git a/src/Sanitiser/Services/SanitizationService.cs b/src/Sanitiser/Services/SanitizationService.cs new file mode 100644 index 0000000..db00b34 --- /dev/null +++ b/src/Sanitiser/Services/SanitizationService.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Options; +using Umbraco.Community.Sanitiser.collections; +using Umbraco.Community.Sanitiser.Configuration; +using Umbraco.Community.Sanitiser.sanitisers; + +namespace Umbraco.Community.Sanitiser.services; + +public class SanitizationService(IOptions options) : ISanitisationService +{ + private readonly SanitiserOptions _options = options.Value; + + public async Task Sanitise(SanitisersCollection sanitisers) + { + // if the sanitization service is enabled then run any sanitizers found + if (IsEnabled()) + { + foreach (ISanitiser sanitiser in sanitisers) + { + // only run enabled sanitisers + if (sanitiser.IsEnabled()) + { + await sanitiser.Sanitise(); + } + } + } + } + + public bool IsEnabled() + { + return _options.Enable; + } +} diff --git a/src/Sanitiser/WebCompositionExtensions.cs b/src/Sanitiser/WebCompositionExtensions.cs new file mode 100644 index 0000000..3f139eb --- /dev/null +++ b/src/Sanitiser/WebCompositionExtensions.cs @@ -0,0 +1,12 @@ +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Community.Sanitiser.collections; + +namespace Umbraco.Community.Sanitiser; + +public static class WebCompositionExtensions +{ + public static SanitisersCollectionBuilder Sanitisers(this IUmbracoBuilder builder) + { + return builder.WithCollectionBuilder(); + } +} diff --git a/src/Sanitiser/appsettings-schema.Sanitiser.json b/src/Sanitiser/appsettings-schema.Sanitiser.json new file mode 100644 index 0000000..4d5adfa --- /dev/null +++ b/src/Sanitiser/appsettings-schema.Sanitiser.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Sanitiser", + "type": "object", + "properties": { + "Sanitiser": { + "$ref": "#/definitions/Sanitiser" + } + }, + "definitions": { + "Sanitiser": { + "type": "object", + "description": "Configuration of Sanitiser", + "properties": { + "Enable": { + "type": "boolean", + "description": "Whether to enable the sanitization service" + }, + "UmbracoFormsSanitiser": { + "type": "object", + "properties": { + "Enable": { + "type": "boolean", + "description": "Whether to enable sanitization of Umbraco Forms submissions" + } + } + }, + "MembersSanitiser": { + "type": "object", + "properties": { + "Enable": { + "type": "boolean", + "description": "Whether to enable sanitization of Umbraco members" + }, + "DomainsToExclude": { + "type": "string", + "description": "A comma separated list of domains to exclude from sanitization. Members with email addresses on those domains are excluded. Leave empty to have all members removed." + } + } + } + } + } + }, + "required": [ + "Sanitiser" + ] +} diff --git a/src/Sanitiser/wwwroot/Sanitiser/lang/en.xml b/src/Sanitiser/wwwroot/Sanitiser/lang/en.xml new file mode 100644 index 0000000..f793c51 --- /dev/null +++ b/src/Sanitiser/wwwroot/Sanitiser/lang/en.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/umbraco-marketplace.json b/umbraco-marketplace.json new file mode 100644 index 0000000..bafbf46 --- /dev/null +++ b/umbraco-marketplace.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://marketplace.umbraco.com/umbraco-marketplace-schema.json", + "AuthorDetails": { + "Name": "Richard Thompson", + "ImageUrl": "https://github.com/richarth.png" + }, + "Category": "Developer Tools", + "Description": "An Umbraco package for automatically clearing out personal data", + "LicenseTypes": [ + "Free" + ], + "IssueTrackerUrl": "https://github.com/richarth/sanitiser/issues", + "PackageType": "Package", + "Tags": ["GDPR", "Data Protection", "Personal Data", "Privacy"], + "Title": "Umbraco Personal Data Sanitiser" +}