diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec deleted file mode 100644 index 3f2aafb259..0000000000 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ /dev/null @@ -1,96 +0,0 @@ - - - - Umbraco.Cms.Core - 9.0.0 - Umbraco Cms Core Binaries - Umbraco HQ - Umbraco HQ - MIT - https://umbraco.com/ - https://umbraco.com/dist/nuget/logo-small.png - false - Contains the core assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms package to setup Umbraco in Visual Studio as an ASP.NET Core project. - Contains the core assemblies needed to run Umbraco Cms - en-US - umbraco - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/build/NuSpecs/UmbracoCms.Examine.Lucene.nuspec b/build/NuSpecs/UmbracoCms.Examine.Lucene.nuspec new file mode 100644 index 0000000000..19d60f27a9 --- /dev/null +++ b/build/NuSpecs/UmbracoCms.Examine.Lucene.nuspec @@ -0,0 +1,49 @@ + + + + Umbraco.Cms.Examine.Lucene + 9.0.0 + Umbraco CMS Examine Binaries + Umbraco HQ + Umbraco HQ + MIT + https://umbraco.com/ + https://umbraco.com/dist/nuget/logo-small.png + false + Contains the Examine assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms package to setup Umbraco in Visual Studio as an ASP.NET Core project. + Contains dll files required to run Examine. + en-US + umbraco + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec deleted file mode 100644 index f12ada7e64..0000000000 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ /dev/null @@ -1,57 +0,0 @@ - - - - Umbraco.Cms.Web - 9.0.0 - Umbraco Cms Core Binaries - Umbraco HQ - Umbraco HQ - MIT - https://umbraco.com/ - https://umbraco.com/dist/nuget/logo-small.png - false - Contains the web assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms package to setup Umbraco in Visual Studio as an ASP.NET Core project. - Contains the core assemblies needed to run Umbraco Cms - en-US - umbraco - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index ef48ce0de2..c031d71704 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -17,7 +17,8 @@ - + + - diff --git a/build/build.ps1 b/build/build.ps1 index 58d56fcdfe..bc39d04083 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -194,14 +194,12 @@ # remove extra files $webAppBin = "$($this.BuildTemp)\WebApp\bin" - $excludeDirs = @("$($webAppBin)\Config","$($webAppBin)\refs","$($webAppBin)\runtimes","$($webAppBin)\Umbraco","$($webAppBin)\wwwroot") + $excludeDirs = @("$($webAppBin)\refs","$($webAppBin)\runtimes","$($webAppBin)\Umbraco","$($webAppBin)\wwwroot") $excludeFiles = @("$($webAppBin)\appsettings.*","$($webAppBin)\*.deps.json","$($webAppBin)\*.exe","$($webAppBin)\*.config","$($webAppBin)\*.runtimeconfig.json") $this.RemoveDirectory($excludeDirs) $this.RemoveFile($excludeFiles) # copy rest of the files into WebApp - $this.CopyFiles("$($this.SolutionRoot)\src\Umbraco.Web.UI.NetCore\Config", "*", "$($this.BuildTemp)\WebApp\config") - $this.RemoveFile("$($this.BuildTemp)\WebApp\Config\*.Release.*") $this.CopyFiles("$($this.SolutionRoot)\src\Umbraco.Web.UI.NetCore\Umbraco", "*", "$($this.BuildTemp)\WebApp\umbraco") $excludeUmbracoDirs = @("$($this.BuildTemp)\WebApp\umbraco\lib") $this.RemoveDirectory($excludeUmbracoDirs) @@ -300,8 +298,6 @@ # create directories Write-Host "Create directories" - mkdir "$tmp\Configs" > $null - mkdir "$tmp\Configs\Lang" > $null mkdir "$tmp\WebApp\App_Data" > $null mkdir "$tmp\Templates" > $null #mkdir "$tmp\WebApp\Media" > $null @@ -311,14 +307,6 @@ Write-Host "Copy xml documentation" Copy-Item -force "$tmp\bin\*.xml" "$tmp\WebApp\bin" - Write-Host "Copy transformed configs and langs" - # note: exclude imageprocessor/*.config as imageprocessor pkg installs them - $this.CopyFiles("$tmp\WebApp\config", "*.config", "$tmp\Configs", ` - { -not $_.RelativeName.StartsWith("imageprocessor") }) - $this.CopyFiles("$tmp\WebApp\config", "*.js", "$tmp\Configs") - $this.CopyFiles("$tmp\WebApp\config\lang", "*.xml", "$tmp\Configs\Lang") - #$this.CopyFile("$tmp\WebApp\web.config", "$tmp\Configs\web.config.transform") - # Write-Host "Copy transformed web.config" # $this.CopyFile("$src\Umbraco.Web.UI\web.$buildConfiguration.Config.transformed", "$tmp\WebApp\web.config") @@ -424,7 +412,7 @@ Write-Host "Restore NuGet" Write-Host "Logging to $($this.BuildTemp)\nuget.restore.log" $params = "-Source", $nugetsourceUmbraco - &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\src\Umbraco.sln" > "$($this.BuildTemp)\nuget.restore.log" @params + &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\src\umbraco-netcore-only.sln" > "$($this.BuildTemp)\nuget.restore.log" @params if (-not $?) { throw "Failed to restore NuGet packages." } }) @@ -435,17 +423,11 @@ Write-Host "Create NuGet packages" - &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Core.nuspec" ` - -Properties BuildTmp="$($this.BuildTemp)" ` - -Version "$($this.Version.Semver.ToString())" ` - -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmscore.log" - if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Core." } - - &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Web.nuspec" ` - -Properties BuildTmp="$($this.BuildTemp)" ` - -Version "$($this.Version.Semver.ToString())" ` - -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmsweb.log" - if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Web." } + &dotnet pack "$($this.SolutionRoot)\src\umbraco-netcore-only.sln" ` + --output "$($this.BuildOutput)" ` + --verbosity detailed ` + -c Release ` + -p:PackageVersion="$($this.Version.Semver.ToString())" > "$($this.BuildTemp)\pack.umbraco.log" &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.nuspec" ` -Properties BuildTmp="$($this.BuildTemp)" ` @@ -453,6 +435,13 @@ -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cms.log" if (-not $?) { throw "Failed to pack NuGet UmbracoCms." } + &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Examine.Lucene.nuspec" ` + -Properties BuildTmp="$($this.BuildTemp)" ` + -Version "$($this.Version.Semver.ToString())" ` + -Verbosity detailed ` + -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.examine.lucene.log" + if (-not $?) { throw "Failed to pack Nuget UmbracoCms.Lucene.nuspec"} + &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.SqlCe.nuspec" ` -Properties BuildTmp="$($this.BuildTemp)" ` -Version "$($this.Version.Semver.ToString())" ` @@ -477,7 +466,7 @@ $ubuild.DefineMethod("VerifyNuGet", { $this.VerifyNuGetConsistency( - ("UmbracoCms", "UmbracoCms.Core", "UmbracoCms.Web"), + ("UmbracoCms"), ("Umbraco.Core", "Umbraco.Infrastructure", "Umbraco.Web.UI.NetCore", "Umbraco.Examine.Lucene", "Umbraco.PublishedCache.NuCache", "Umbraco.Web.Common", "Umbraco.Web.Website", "Umbraco.Web.BackOffice", "Umbraco.Persistence.SqlCe")) if ($this.OnError()) { return } }) diff --git a/build/templates/UmbracoSolution/.template.config/template.json b/build/templates/UmbracoSolution/.template.config/template.json index 7f69b5b0e8..a85a4f4af8 100644 --- a/build/templates/UmbracoSolution/.template.config/template.json +++ b/build/templates/UmbracoSolution/.template.config/template.json @@ -15,7 +15,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "0.5.0-alpha003", + "defaultValue": "9.0.0-alpha004", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/src/Directory.Build.props b/src/Directory.Build.props index cdce38df2f..c4346a0603 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,12 +2,20 @@ 9.0.0 9.0.0 - 9.0.0-beta001 + 9.0.0-alpha004 9.0.0 9.0 en-US Umbraco CMS Copyright © Umbraco 2021 + Umbraco HQ + https://umbraco.com/ + https://umbraco.com/dist/nuget/logo-small.png + https://opensource.org/licenses/MIT + false + umbraco + git + https://github.com/umbraco/umbraco-cms diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs index 680c47590e..ab6a7e9396 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Hosting; @@ -51,6 +52,16 @@ namespace Umbraco.Cms.Core.Configuration.Grid _logger.LogError(ex, "Could not parse the contents of grid.editors.config.js into a JSON array '{Json}", sourceString); } } + else// Read default from embedded file + { + var assembly = GetType().Assembly; + var resourceStream = assembly.GetManifestResourceStream( + "Umbraco.Cms.Core.EmbeddedResources.Grid.grid.editors.config.js"); + + using var reader = new StreamReader(resourceStream, Encoding.UTF8); + var sourceString = reader.ReadToEnd(); + editors.AddRange(_jsonSerializer.Deserialize>(sourceString)); + } // add manifest editors, skip duplicates foreach (var gridEditor in _manifestParser.Manifest.GridEditors) diff --git a/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs new file mode 100644 index 0000000000..cdd88ca409 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Models.ContentEditing; + +namespace Umbraco.Cms.Core.Configuration.Models +{ + public class RichTextEditorSettings + { + private static readonly string[] s_default_plugins = new[] + { + "paste", + "anchor", + "charmap", + "table", + "lists", + "advlist", + "hr", + "autolink", + "directionality", + "tabfocus", + "searchreplace" + }; + private static readonly RichTextEditorCommand[] s_default_commands = new [] + { + new RichTextEditorCommand(){Alias = "ace" , Name = "Source code editor" , Mode = RichTextEditorCommandMode.Insert}, + new RichTextEditorCommand(){Alias = "removeformat" , Name = "Remove format" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "undo" , Name = "Undo" , Mode = RichTextEditorCommandMode.Insert}, + new RichTextEditorCommand(){Alias = "redo" , Name = "Redo" , Mode = RichTextEditorCommandMode.Insert}, + new RichTextEditorCommand(){Alias = "cut" , Name = "Cut" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "copy" , Name = "Copy" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "paste" , Name = "Paste" , Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand(){Alias = "styleselect" , Name = "Style select" , Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand(){Alias = "bold" , Name = "Bold" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "italic" , Name = "Italic" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "underline" , Name = "Underline" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "strikethrough" , Name = "Strikethrough" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "alignleft" , Name = "Justify left" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "aligncenter" , Name = "Justify center" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "alignright" , Name = "Justify right" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "alignjustify" , Name = "Justify full" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "bullist" , Name = "Bullet list" , Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand(){Alias = "numlist" , Name = "Numbered list" , Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand(){Alias = "outdent" , Name = "Decrease indent" , Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand(){Alias = "indent" , Name = "Increase indent" , Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand(){Alias = "link" , Name = "Insert/edit link" , Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand(){Alias = "unlink" , Name = "Remove link" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "anchor" , Name = "Anchor" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "umbmediapicker" , Name = "Image" , Mode = RichTextEditorCommandMode.Insert}, + new RichTextEditorCommand(){Alias = "umbmacro" , Name = "Macro" , Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand(){Alias = "table" , Name = "Table" , Mode = RichTextEditorCommandMode.Insert}, + new RichTextEditorCommand(){Alias = "umbembeddialog" , Name = "Embed" , Mode = RichTextEditorCommandMode.Insert}, + new RichTextEditorCommand(){Alias = "hr" , Name = "Horizontal rule" , Mode = RichTextEditorCommandMode.Insert}, + new RichTextEditorCommand(){Alias = "subscript" , Name = "Subscript" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "superscript" , Name = "Superscript" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "charmap" , Name = "Character map" , Mode = RichTextEditorCommandMode.Insert}, + new RichTextEditorCommand(){Alias = "rtl" , Name = "Right to left" , Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand(){Alias = "ltr" , Name = "Left to right" , Mode = RichTextEditorCommandMode.Selection}, + }; + + private static readonly IDictionary s_default_custom_config = new Dictionary() + { + ["entity_encoding"] = "raw" + }; + + public RichTextEditorCommand[] Commands { get; set; } = s_default_commands; + public string[] Plugins { get; set; } = s_default_plugins; + public IDictionary CustomConfig { get; set; } = s_default_custom_config; + public string ValidElements { get; set; } = "+a[id|style|rel|data-id|data-udi|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel|data-id],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align|style],hr[class|style],small[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*]"; + public string InvalidElements { get; set; } = "font"; + + public class RichTextEditorCommand + { + public string Alias { get; set; } + public string Name { get; set; } + public RichTextEditorCommandMode Mode { get; set; } + } + } +} diff --git a/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs b/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs index 3d620ee9e5..0f397a749f 100644 --- a/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs +++ b/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs @@ -1,6 +1,6 @@ -using System.Configuration; -using System.IO; +using System.IO; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Exceptions; using Umbraco.Cms.Core.Hosting; namespace Umbraco.Extensions @@ -30,7 +30,7 @@ namespace Umbraco.Extensions // unless AcceptUnsafeModelsDirectory and then everything is OK. if (!Path.IsPathRooted(root)) - throw new ConfigurationErrorsException($"Root is not rooted \"{root}\"."); + throw new ConfigurationException($"Root is not rooted \"{root}\"."); if (config.StartsWith("~/")) { @@ -43,7 +43,7 @@ namespace Umbraco.Extensions root = Path.GetFullPath(root); if (!dir.StartsWith(root) && !acceptUnsafe) - throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); + throw new ConfigurationException($"Invalid models directory \"{config}\"."); return dir; } @@ -51,7 +51,7 @@ namespace Umbraco.Extensions if (acceptUnsafe) return Path.GetFullPath(config); - throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); + throw new ConfigurationException($"Invalid models directory \"{config}\"."); } } } diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs index 0d62094dad..d596d3feec 100644 --- a/src/Umbraco.Core/Constants-Configuration.cs +++ b/src/Umbraco.Core/Constants-Configuration.cs @@ -50,6 +50,7 @@ public const string ConfigTypeFinder = ConfigPrefix + "TypeFinder"; public const string ConfigWebRouting = ConfigPrefix + "WebRouting"; public const string ConfigUserPassword = ConfigPrefix + "Security:UserPassword"; + public const string ConfigRichTextEditor = ConfigPrefix + "RichTextEditor"; } } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 47a98ea9e1..c37f8fb760 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -56,6 +56,7 @@ namespace Umbraco.Cms.Core.DependencyInjection AddOptions(builder, Constants.Configuration.ConfigWebRouting); AddOptions(builder, Constants.Configuration.ConfigPlugins); AddOptions(builder, Constants.Configuration.ConfigUnattended); + AddOptions(builder, Constants.Configuration.ConfigRichTextEditor); return builder; } diff --git a/src/Umbraco.Web.UI.NetCore/config/grid.editors.config.js b/src/Umbraco.Core/EmbeddedResources/Grid/grid.editors.config.js similarity index 100% rename from src/Umbraco.Web.UI.NetCore/config/grid.editors.config.js rename to src/Umbraco.Core/EmbeddedResources/Grid/grid.editors.config.js diff --git a/src/Umbraco.Core/Exceptions/ConfigurationException.cs b/src/Umbraco.Core/Exceptions/ConfigurationException.cs new file mode 100644 index 0000000000..fe711a9823 --- /dev/null +++ b/src/Umbraco.Core/Exceptions/ConfigurationException.cs @@ -0,0 +1,41 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Cms.Core.Exceptions +{ + /// + /// An exception that is thrown if the configuration is wrong. + /// + /// + [Serializable] + public class ConfigurationException : Exception + { + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public ConfigurationException(string message) + : base(message) + { } + + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception which is the cause of this exception. + /// + /// The message that describes the error. + /// The inner exception, or null. + public ConfigurationException(string message, Exception innerException) + : base(message, innerException) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected ConfigurationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + + } +} diff --git a/src/Umbraco.Core/Models/ILogViewerQuery.cs b/src/Umbraco.Core/Models/ILogViewerQuery.cs new file mode 100644 index 0000000000..3b36f0a9e8 --- /dev/null +++ b/src/Umbraco.Core/Models/ILogViewerQuery.cs @@ -0,0 +1,10 @@ +using Umbraco.Cms.Core.Models.Entities; + +namespace Umbraco.Cms.Core.Models +{ + public interface ILogViewerQuery : IEntity + { + string Name { get; set; } + string Query { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/LogViewerQuery.cs b/src/Umbraco.Core/Models/LogViewerQuery.cs new file mode 100644 index 0000000000..5addfa705f --- /dev/null +++ b/src/Umbraco.Core/Models/LogViewerQuery.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.Serialization; +using Umbraco.Cms.Core.Models.Entities; + +namespace Umbraco.Cms.Core.Models +{ + [Serializable] + [DataContract(IsReference = true)] + public class LogViewerQuery : EntityBase, ILogViewerQuery + { + private string _name; + private string _query; + + public LogViewerQuery(string name, string query) + { + Name = name; + _query = query; + } + + [DataMember] + public string Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); + } + + [DataMember] + public string Query + { + get => _query; + set => SetPropertyValueAndDetectChanges(value, ref _query, nameof(Query)); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs index a98c72b393..8a2302dcff 100644 --- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs +++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs @@ -76,6 +76,8 @@ namespace Umbraco.Cms.Core public const string AuditEntry = TableNamePrefix + "Audit"; public const string Consent = TableNamePrefix + "Consent"; public const string UserLogin = TableNamePrefix + "UserLogin"; + + public const string LogViewerQuery = "cms" + "LogViewerQuery"; } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/ILogViewerQueryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ILogViewerQueryRepository.cs new file mode 100644 index 0000000000..d21cd2aa1e --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/ILogViewerQueryRepository.cs @@ -0,0 +1,9 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Persistence.Repositories +{ + public interface ILogViewerQueryRepository : IReadWriteQueryRepository + { + ILogViewerQuery GetByName(string name); + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index bcd73a4a8b..ce524a09a1 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1,9 +1,13 @@ - + netstandard2.0 Umbraco.Cms.Core - Umbraco CMS + Umbraco CMS + Umbraco.Cms.Core + Umbraco CMS Core + Contains the core assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco + Umbraco CMS @@ -12,13 +16,13 @@ + - @@ -43,4 +47,8 @@ <_Parameter1>DynamicProxyGenAssembly2 + + + + diff --git a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj index 43f06ceef3..329c11f879 100644 --- a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj +++ b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj @@ -5,6 +5,11 @@ Umbraco.Cms.Infrastructure.Examine Umbraco CMS Umbraco.Examine.Lucene + + + false + + Umbraco.Cms.Examine.Lucene diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index ec9031c497..e3b327ffaa 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -125,8 +125,6 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); - builder.Services.AddUnique(); - // register *all* checks, except those marked [HideFromTypeFinder] of course builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs index 8292fd2ecb..710565500e 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs @@ -55,6 +55,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); + builder.Services.AddUnique(); return builder; } diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs index da17bad085..a758b08081 100644 --- a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs +++ b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs @@ -1,85 +1,45 @@ using System.Collections.Generic; -using System.IO; using System.Linq; -using Newtonsoft.Json; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Routing; -using Formatting = Newtonsoft.Json.Formatting; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; namespace Umbraco.Cms.Core.Logging.Viewer { public class LogViewerConfig : ILogViewerConfig { - private readonly IHostingEnvironment _hostingEnvironment; - private static readonly string _pathToSearches = WebPath.Combine(Cms.Core.Constants.SystemDirectories.Config, "logviewer.searches.config.js"); - private readonly FileInfo _searchesConfig; + private readonly ILogViewerQueryRepository _logViewerQueryRepository; + private readonly IScopeProvider _scopeProvider; - public LogViewerConfig(IHostingEnvironment hostingEnvironment) + public LogViewerConfig(ILogViewerQueryRepository logViewerQueryRepository, IScopeProvider scopeProvider) { - _hostingEnvironment = hostingEnvironment; - var trimmedPath = _pathToSearches.TrimStart(Constants.CharArrays.TildeForwardSlash).Replace('/', Path.DirectorySeparatorChar); - var absolutePath = Path.Combine(_hostingEnvironment.ApplicationPhysicalPath, trimmedPath); - _searchesConfig = new FileInfo(absolutePath); + _logViewerQueryRepository = logViewerQueryRepository; + _scopeProvider = scopeProvider; } public IReadOnlyList GetSavedSearches() { - //Our default implementation - - //If file does not exist - lets create it with an empty array - EnsureFileExists(); - - var rawJson = System.IO.File.ReadAllText(_searchesConfig.FullName); - return JsonConvert.DeserializeObject(rawJson); + using var scope = _scopeProvider.CreateScope(autoComplete: true); + var logViewerQueries = _logViewerQueryRepository.GetMany(); + var result = logViewerQueries.Select(x => new SavedLogSearch() { Name = x.Name, Query = x.Query }).ToArray(); + return result; } public IReadOnlyList AddSavedSearch(string name, string query) { - //Get the existing items - var searches = GetSavedSearches().ToList(); + using var scope = _scopeProvider.CreateScope(autoComplete: true); + _logViewerQueryRepository.Save(new LogViewerQuery(name, query)); - //Add the new item to the bottom of the list - searches.Add(new SavedLogSearch { Name = name, Query = query }); - - //Serialize to JSON string - var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented); - - //If file does not exist - lets create it with an empty array - EnsureFileExists(); - - //Write it back down to file - System.IO.File.WriteAllText(_searchesConfig.FullName, rawJson); - - //Return the updated object - so we can instantly reset the entire array from the API response - //As opposed to push a new item into the array - return searches; + return GetSavedSearches(); } public IReadOnlyList DeleteSavedSearch(string name, string query) { - //Get the existing items - var searches = GetSavedSearches().ToList(); - - //Removes the search - searches.RemoveAll(s => s.Name.Equals(name) && s.Query.Equals(query)); - - //Serialize to JSON string - var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented); - - //Write it back down to file - System.IO.File.WriteAllText(_searchesConfig.FullName, rawJson); - + using var scope = _scopeProvider.CreateScope(autoComplete: true); + var item = _logViewerQueryRepository.GetByName(name); + _logViewerQueryRepository.Delete(item); //Return the updated object - so we can instantly reset the entire array from the API response - return searches; - } - - private void EnsureFileExists() - { - if (_searchesConfig.Exists) return; - using (var writer = _searchesConfig.CreateText()) - { - writer.Write("[]"); - } + return GetSavedSearches(); } } } diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index 72e8b864bf..30759ae789 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -1,9 +1,11 @@ using System; +using System.Linq; using Microsoft.Extensions.Logging; using NPoco; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Infrastructure.Migrations.Upgrade; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Extensions; @@ -76,6 +78,9 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.KeyValue)) CreateKeyValueData(); + if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery)) + CreateLogViewerQueryData(); + _logger.LogInformation("Done creating table {TableName} data.", tableName); } @@ -345,5 +350,17 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.KeyValue, "key", false, new KeyValueDto { Key = stateValueKey, Value = finalState, UpdateDate = DateTime.Now }); } + + private void CreateLogViewerQueryData() + { + var defaultData = MigrateLogViewerQueriesFromFileToDb.DefaultLogQueries.ToArray(); + + for (int i = 0; i < defaultData.Length; i++) + { + var dto = defaultData[i]; + dto.Id = i+1; + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery, "id", false, dto); + } + } } } diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs index 10564f7c98..0225a60162 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs @@ -92,7 +92,8 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install typeof (AuditEntryDto), typeof (ContentVersionCultureVariationDto), typeof (DocumentCultureVariationDto), - typeof (ContentScheduleDto) + typeof (ContentScheduleDto), + typeof (LogViewerQueryDto) }; /// diff --git a/src/Umbraco.Infrastructure/Migrations/PostMigrations/DeleteLogViewerQueryFile.cs b/src/Umbraco.Infrastructure/Migrations/PostMigrations/DeleteLogViewerQueryFile.cs new file mode 100644 index 0000000000..cc1828dc2e --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/PostMigrations/DeleteLogViewerQueryFile.cs @@ -0,0 +1,34 @@ +using System.IO; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Migrations; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0; + +namespace Umbraco.Cms.Infrastructure.Migrations.PostMigrations +{ + /// + /// Deletes the old file that saved log queries + /// + public class DeleteLogViewerQueryFile : IMigration + { + private readonly IHostingEnvironment _hostingEnvironment; + + /// + /// Initializes a new instance of the class. + /// + public DeleteLogViewerQueryFile(IMigrationContext context, IHostingEnvironment hostingEnvironment) + { + _hostingEnvironment = hostingEnvironment; + } + + /// + public void Migrate() + { + var logViewerQueryFile = MigrateLogViewerQueriesFromFileToDb.GetLogViewerQueryFile(_hostingEnvironment); + + if(File.Exists(logViewerQueryFile)) + { + File.Delete(logViewerQueryFile); + } + } + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index da3325ef64..3879549e43 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Semver; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.Common; @@ -201,7 +201,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade To("{D6A8D863-38EC-44FB-91EC-ACD6A668BD18}"); // to 9.0.0 - + To("{22D801BA-A1FF-4539-BFCC-2139B55594F8}"); To("{50A43237-A6F4-49E2-A7A6-5DAD65C84669}"); To("{3D8DADEF-0FDA-4377-A5F0-B52C2110E8F2}"); diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/MigrateLogViewerQueriesFromFileToDb.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/MigrateLogViewerQueriesFromFileToDb.cs new file mode 100644 index 0000000000..88ffe1a66c --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/MigrateLogViewerQueriesFromFileToDb.cs @@ -0,0 +1,108 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Newtonsoft.Json; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Infrastructure.Migrations.PostMigrations; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0 +{ + + public class MigrateLogViewerQueriesFromFileToDb : MigrationBase + { + private readonly IHostingEnvironment _hostingEnvironment; + internal static readonly IEnumerable DefaultLogQueries = new LogViewerQueryDto[] + { + new (){ + Name = "Find all logs where the Level is NOT Verbose and NOT Debug", + Query = "Not(@Level='Verbose') and Not(@Level='Debug')" + }, + new (){ + Name = "Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)", + Query = "Has(@Exception)" + }, + new (){ + Name = "Find all logs that have the property 'Duration'", + Query = "Has(Duration)" + }, + new (){ + Name = "Find all logs that have the property 'Duration' and the duration is greater than 1000ms", + Query = "Has(Duration) and Duration > 1000" + }, + new (){ + Name = "Find all logs that are from the namespace 'Umbraco.Core'", + Query = "StartsWith(SourceContext, 'Umbraco.Core')" + }, + new (){ + Name = "Find all logs that use a specific log message template", + Query = "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'" + }, + new (){ + Name = "Find logs where one of the items in the SortedComponentTypes property array is equal to", + Query = "SortedComponentTypes[?] = 'Umbraco.Web.Search.ExamineComponent'" + }, + new (){ + Name = "Find logs where one of the items in the SortedComponentTypes property array contains", + Query = "Contains(SortedComponentTypes[?], 'DatabaseServer')" + }, + new (){ + Name = "Find all logs that the message has localhost in it with SQL like", + Query = "@Message like '%localhost%'" + }, + new (){ + Name = "Find all logs that the message that starts with 'end' in it with SQL like", + Query = "@Message like 'end%'" + } + }; + + public MigrateLogViewerQueriesFromFileToDb(IMigrationContext context, IHostingEnvironment hostingEnvironment) + : base(context) + { + _hostingEnvironment = hostingEnvironment; + } + + public override void Migrate() + { + Debugger.Launch(); + Debugger.Break(); + CreateDatabaseTable(); + MigrateFileContentToDB(); + } + private void CreateDatabaseTable() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database); + if (!tables.InvariantContains(Core.Constants.DatabaseSchema.Tables.LogViewerQuery)) + { + Create.Table().Do(); + } + } + + internal static string GetLogViewerQueryFile(IHostingEnvironment hostingEnvironment) + { + return hostingEnvironment.MapPathContentRoot( + Path.Combine(Cms.Core.Constants.SystemDirectories.Config, "logviewer.searches.config.js")); + } + private void MigrateFileContentToDB() + { + var logViewerQueryFile = GetLogViewerQueryFile(_hostingEnvironment); + + var logQueriesInFile = File.Exists(logViewerQueryFile) ? + JsonConvert.DeserializeObject(File.ReadAllText(logViewerQueryFile)) + : DefaultLogQueries; + + var logQueriesInDb = Database.Query().ToArray(); + + if (logQueriesInDb.Any()) + { + return; + } + + Database.InsertBulk(logQueriesInFile); + + Context.AddPostMigration(); + } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/LogViewerQueryDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/LogViewerQueryDto.cs new file mode 100644 index 0000000000..71642c8b73 --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/LogViewerQueryDto.cs @@ -0,0 +1,22 @@ +using NPoco; +using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; + +namespace Umbraco.Cms.Infrastructure.Persistence.Dtos +{ + [TableName(Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class LogViewerQueryDto + { + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + [Column("name")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_LogViewerQuery_name")] + public string Name { get; set; } + + [Column("query")] + public string Query { get; set; } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Mappers/LogViewerQueryMapper.cs b/src/Umbraco.Infrastructure/Persistence/Mappers/LogViewerQueryMapper.cs new file mode 100644 index 0000000000..807e3b6c02 --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Mappers/LogViewerQueryMapper.cs @@ -0,0 +1,22 @@ +using System; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; + +namespace Umbraco.Cms.Infrastructure.Persistence.Mappers +{ + [MapperFor(typeof(ILogViewerQuery))] + [MapperFor(typeof(LogViewerQuery))] + public sealed class LogViewerQueryMapper : BaseMapper + { + public LogViewerQueryMapper(Lazy sqlContext, MapperConfigurationStore maps) + : base(sqlContext, maps) + { } + + protected override void DefineMaps() + { + DefineMap(nameof(ILogViewerQuery.Id), nameof(LogViewerQueryDto.Id)); + DefineMap(nameof(ILogViewerQuery.Name), nameof(LogViewerQueryDto.Name)); + DefineMap(nameof(ILogViewerQuery.Query), nameof(LogViewerQueryDto.Query)); + } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Mappers/MapperCollectionBuilder.cs b/src/Umbraco.Infrastructure/Persistence/Mappers/MapperCollectionBuilder.cs index d200b6fdc1..e797319810 100644 --- a/src/Umbraco.Infrastructure/Persistence/Mappers/MapperCollectionBuilder.cs +++ b/src/Umbraco.Infrastructure/Persistence/Mappers/MapperCollectionBuilder.cs @@ -54,6 +54,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Mappers Add(); Add(); Add(); + Add(); return this; } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LogViewerQueryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LogViewerQueryRepository.cs new file mode 100644 index 0000000000..35f0b8fdab --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LogViewerQueryRepository.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Microsoft.Extensions.Logging; +using NPoco; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Querying; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement +{ + internal class LogViewerQueryRepository : EntityRepositoryBase, ILogViewerQueryRepository + { + public LogViewerQueryRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) + : base(scopeAccessor, cache, logger) + { } + + protected override IRepositoryCachePolicy CreateCachePolicy() + { + return new FullDataSetRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/ false); + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + var sql = GetBaseQuery(false).Where($"{Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery}.id > 0"); + if (ids.Any()) + { + sql.Where($"{Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery}.id in (@ids)", new { ids = ids }); + } + + return Database.Fetch(sql).Select(ConvertFromDto); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotSupportedException("This repository does not support this method"); + } + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = Sql(); + sql = isCount ? sql.SelectCount() : sql.Select(); + sql = sql.From(); + return sql; + } + + protected override string GetBaseWhereClause() + { + return $"{Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery}.id = @id"; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + $"DELETE FROM {Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery} WHERE id = @id" + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { throw new NotImplementedException(); } + } + + protected override void PersistNewItem(ILogViewerQuery entity) + { + var exists = Database.ExecuteScalar($"SELECT COUNT(*) FROM {Core.Constants.DatabaseSchema.Tables.LogViewerQuery} WHERE name = @name", + new { name = entity.Name }); + if (exists > 0) throw new DuplicateNameException($"The log query name '{entity.Name}' is already used"); + + entity.AddingEntity(); + + var factory = new LogViewerQueryModelFactory(); + var dto = factory.BuildDto(entity); + + var id = Convert.ToInt32(Database.Insert(dto)); + entity.Id = id; + } + + protected override void PersistUpdatedItem(ILogViewerQuery entity) + { + entity.UpdatingEntity(); + + var exists = Database.ExecuteScalar($"SELECT COUNT(*) FROM {Core.Constants.DatabaseSchema.Tables.LogViewerQuery} WHERE name = @name AND id <> @id", + new { name = entity.Name, id = entity.Id }); + //ensure there is no other log query with the same name on another entity + if (exists > 0) throw new DuplicateNameException($"The log query name '{entity.Name}' is already used"); + + + var factory = new LogViewerQueryModelFactory(); + var dto = factory.BuildDto(entity); + + Database.Update(dto); + } + + private ILogViewerQuery ConvertFromDto(LogViewerQueryDto dto) + { + var factory = new LogViewerQueryModelFactory(); + var entity = factory.BuildEntity(dto); + return entity; + } + + internal class LogViewerQueryModelFactory + { + + public ILogViewerQuery BuildEntity(LogViewerQueryDto dto) + { + var logViewerQuery = new LogViewerQuery(dto.Name, dto.Query) + { + Id = dto.Id, + }; + return logViewerQuery; + } + + public LogViewerQueryDto BuildDto(ILogViewerQuery entity) + { + var dto = new LogViewerQueryDto { Name = entity.Name, Query = entity.Query, Id = entity.Id }; + return dto; + } + } + + protected override ILogViewerQuery PerformGet(int id) + { + //use the underlying GetAll which will force cache all log queries + return GetMany().FirstOrDefault(x => x.Id == id); + } + + public ILogViewerQuery GetByName(string name) + { + //use the underlying GetAll which will force cache all log queries + return GetMany().FirstOrDefault(x => x.Name == name); + } + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs index a40ab92ca8..6e732cdc0f 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs @@ -202,7 +202,7 @@ namespace Umbraco.Cms.Core.PropertyEditors _richTextPropertyValueEditor.GetReferences(x.Value))) yield return umbracoEntityReference; - foreach (var umbracoEntityReference in mediaValues.SelectMany(x => + foreach (var umbracoEntityReference in mediaValues.Where(x=>x.Value.HasValues).SelectMany(x => _mediaPickerPropertyValueEditor.GetReferences(x.Value["udi"]))) yield return umbracoEntityReference; } diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index 23c1af2c5d..99642069f3 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -3,6 +3,9 @@ netstandard2.0 Umbraco.Cms.Infrastructure + Umbraco.Cms.Infrastructure + Umbraco CMS Infrastructure + Contains the infrastructure assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco diff --git a/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj b/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj index fe3ce47de4..9aea12912c 100644 --- a/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj +++ b/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj @@ -3,6 +3,10 @@ netstandard2.0 Umbraco.Cms.Infrastructure.PublishedCache + 8 + Umbraco.Cms.PublishedCache.NuCache + Umbraco CMS Published Cache + Contains the Published Cache assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco diff --git a/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj b/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj index 1040df225d..a13d6b763b 100644 --- a/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj +++ b/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj @@ -3,6 +3,9 @@ netstandard2.0 Umbraco.Cms.Tests.Common + Umbraco.Cms.Tests + Umbraco CMS Test Tools + Contains commonly used tools to write tests for Umbraco CMS, such as various builders for content etc. diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs index a44d7ca252..55a348aad7 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs @@ -220,6 +220,19 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence } } + [Test] + public void Can_Create_umbracoLogViewerQuery_Table() + { + using (var scope = ScopeProvider.CreateScope()) + { + var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion); + + helper.CreateTable(); + + scope.Complete(); + } + } + [Test] public void Can_Create_umbracoLanguage_Table() { diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/Assets/logviewer.searches.config.js b/src/Umbraco.Tests.UnitTests/TestHelpers/Assets/logviewer.searches.config.js deleted file mode 100644 index 25ee9b2242..0000000000 --- a/src/Umbraco.Tests.UnitTests/TestHelpers/Assets/logviewer.searches.config.js +++ /dev/null @@ -1,42 +0,0 @@ -[ - { - "name": "Find all logs where the Level is NOT Verbose and NOT Debug", - "query": "Not(@Level='Verbose') and Not(@Level='Debug')" - }, - { - "name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)", - "query": "Has(@Exception)" - }, - { - "name": "Find all logs that have the property 'Duration'", - "query": "Has(Duration)" - }, - { - "name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms", - "query": "Has(Duration) and Duration > 1000" - }, - { - "name": "Find all logs that are from the namespace 'Umbraco.Core'", - "query": "StartsWith(SourceContext, 'Umbraco.Core')" - }, - { - "name": "Find all logs that use a specific log message template", - "query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'" - }, - { - "name": "Find logs where one of the items in the SortedComponentTypes property array is equal to", - "query": "SortedComponentTypes[?] = 'Umbraco.Web.Search.ExamineComponent'" - }, - { - "name": "Find logs where one of the items in the SortedComponentTypes property array contains", - "query": "Contains(SortedComponentTypes[?], 'DatabaseServer')" - }, - { - "name": "Find all logs that the message has localhost in it with SQL like", - "query": "@Message like '%localhost%'" - }, - { - "name": "Find all logs that the message that starts with 'end' in it with SQL like", - "query": "@Message like 'end%'" - } -] diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs index a957f3611e..9ba522fbc9 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs @@ -16,6 +16,11 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Logging.Viewer; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Querying; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0; +using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Tests.UnitTests.TestHelpers; using File = System.IO.File; @@ -27,18 +32,16 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Logging private ILogViewer _logViewer; private const string LogfileName = "UmbracoTraceLog.UNITTEST.20181112.json"; - private const string SearchfileName = "logviewer.searches.config.js"; private string _newLogfilePath; private string _newLogfileDirPath; - private string _newSearchfilePath; - private string _newSearchfileDirPath; - private readonly LogTimePeriod _logTimePeriod = new LogTimePeriod( new DateTime(year: 2018, month: 11, day: 12, hour: 0, minute: 0, second: 0), new DateTime(year: 2018, month: 11, day: 13, hour: 0, minute: 0, second: 0)); + private ILogViewerQueryRepository LogViewerQueryRepository { get; } = new TestLogViewerQueryRepository(); + [OneTimeSetUp] public void Setup() { @@ -55,20 +58,14 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Logging _newLogfileDirPath = loggingConfiguration.LogDirectory; _newLogfilePath = Path.Combine(_newLogfileDirPath, LogfileName); - var exampleSearchfilePath = Path.Combine(testRoot, "TestHelpers", "Assets", SearchfileName); - _newSearchfileDirPath = Path.Combine(hostingEnv.ApplicationPhysicalPath, @"config"); - _newSearchfilePath = Path.Combine(_newSearchfileDirPath, SearchfileName); - // Create/ensure Directory exists ioHelper.EnsurePathExists(_newLogfileDirPath); - ioHelper.EnsurePathExists(_newSearchfileDirPath); // Copy the sample files File.Copy(exampleLogfilePath, _newLogfilePath, true); - File.Copy(exampleSearchfilePath, _newSearchfilePath, true); ILogger logger = Mock.Of>(); - var logViewerConfig = new LogViewerConfig(hostingEnv); + var logViewerConfig = new LogViewerConfig(LogViewerQueryRepository, Mock.Of()); _logViewer = new SerilogJsonLogViewer(logger, logViewerConfig, loggingConfiguration, Log.Logger); } @@ -81,11 +78,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Logging { File.Delete(_newLogfilePath); } - - if (File.Exists(_newSearchfilePath)) - { - File.Delete(_newSearchfilePath); - } } [Test] @@ -238,4 +230,55 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Logging Assert.IsEmpty(findItem, "The search item should no longer exist"); } } + + internal class TestLogViewerQueryRepository : ILogViewerQueryRepository + { + public TestLogViewerQueryRepository() + { + Store = new List(MigrateLogViewerQueriesFromFileToDb.DefaultLogQueries + .Select(LogViewerQueryModelFactory.BuildEntity)); + } + + private IList Store { get; } + private LogViewerQueryRepository.LogViewerQueryModelFactory LogViewerQueryModelFactory { get; } = new LogViewerQueryRepository.LogViewerQueryModelFactory(); + + + public ILogViewerQuery Get(int id) => Store.FirstOrDefault(x => x.Id == id); + + public IEnumerable GetMany(params int[] ids) => + ids.Any() ? Store.Where(x => ids.Contains(x.Id)) : Store; + + public bool Exists(int id) => Get(id) is not null; + + public void Save(ILogViewerQuery entity) + { + var item = Get(entity.Id); + + if (item is null) + { + Store.Add(entity); + } + else + { + item.Name = entity.Name; + item.Query = entity.Query; + } + } + + public void Delete(ILogViewerQuery entity) + { + var item = Get(entity.Id); + + if (item is not null) + { + Store.Remove(item); + } + } + + public IEnumerable Get(IQuery query) => throw new NotImplementedException(); + + public int Count(IQuery query) => throw new NotImplementedException(); + + public ILogViewerQuery GetByName(string name) => Store.FirstOrDefault(x => x.Name == name); + } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index 11e44d7dce..1bd1600721 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -26,7 +26,6 @@ - diff --git a/src/Umbraco.Web.BackOffice/Controllers/TourController.cs b/src/Umbraco.Web.BackOffice/Controllers/TourController.cs index 607df48701..3b6116cc6c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/TourController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TourController.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; +using System.Threading.Tasks; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Umbraco.Cms.Core; @@ -39,7 +41,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _contentTypeService = contentTypeService; } - public IEnumerable GetTours() + public async Task> GetTours() { var result = new List(); @@ -57,15 +59,17 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var nonPluginFilters = _filters.Where(x => x.PluginName == null).ToList(); //add core tour files - var coreToursPath = Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Config), "BackOfficeTours"); - if (Directory.Exists(coreToursPath)) + var embeddedTourNames = GetType() + .Assembly + .GetManifestResourceNames() + .Where(x => x.StartsWith("Umbraco.Cms.Web.BackOffice.EmbeddedResources.Tours.")); + + foreach (var embeddedTourName in embeddedTourNames) { - foreach (var tourFile in Directory.EnumerateFiles(coreToursPath, "*.json")) - { - TryParseTourFile(tourFile, result, nonPluginFilters, aliasOnlyFilters); - } + await TryParseTourFile(embeddedTourName, result, nonPluginFilters, aliasOnlyFilters, async x=> await GetContentFromEmbeddedResource(x)); } + //collect all tour files in packages var appPlugins = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins); if (Directory.Exists(appPlugins)) @@ -89,7 +93,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { foreach (var tourFile in Directory.EnumerateFiles(tourDir, "*.json")) { - TryParseTourFile(tourFile, result, combinedFilters, aliasOnlyFilters, pluginName); + await TryParseTourFile(tourFile, result, combinedFilters, aliasOnlyFilters, async x => await System.IO.File.ReadAllTextAsync(x), pluginName); } } } @@ -126,14 +130,22 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return result.Except(toursToBeRemoved).OrderBy(x => x.FileName, StringComparer.InvariantCultureIgnoreCase); } + private async Task GetContentFromEmbeddedResource(string fileName) + { + var resourceStream = GetType().Assembly.GetManifestResourceStream(fileName); + + using var reader = new StreamReader(resourceStream, Encoding.UTF8); + return await reader.ReadToEndAsync(); + } + /// /// Gets a tours for a specific doctype /// /// The documenttype alias /// A - public IEnumerable GetToursForDoctype(string doctypeAlias) + public async Task> GetToursForDoctype(string doctypeAlias) { - var tourFiles = this.GetTours(); + var tourFiles = await this.GetTours(); var doctypeAliasWithCompositions = new List { @@ -159,10 +171,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers }); } - private void TryParseTourFile(string tourFile, + private async Task TryParseTourFile(string tourFile, ICollection result, List filters, List aliasOnlyFilters, + Func> fileNameToFileContent, string pluginName = null) { var fileName = Path.GetFileNameWithoutExtension(tourFile); @@ -182,7 +195,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers try { - var contents = System.IO.File.ReadAllText(tourFile); + var contents = await fileNameToFileContent(tourFile); var tours = JsonConvert.DeserializeObject(contents); var backOfficeTours = tours.Where(x => diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/ServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/ServiceCollectionExtensions.cs index 9c4caac73e..3218897eee 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/ServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Security; @@ -11,7 +12,6 @@ using Umbraco.Cms.Web.BackOffice.Security; using Umbraco.Cms.Web.Common.AspNetCore; using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Cms.Web.Common.Security; -using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Extensions { diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index 3d17e6f67c..098b2ba879 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -81,7 +81,6 @@ namespace Umbraco.Extensions builder.Services.ConfigureOptions(); - builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); diff --git a/src/Umbraco.Web.UI.NetCore/config/BackOfficeTours/getting-started.json b/src/Umbraco.Web.BackOffice/EmbeddedResources/Tours/getting-started.json similarity index 100% rename from src/Umbraco.Web.UI.NetCore/config/BackOfficeTours/getting-started.json rename to src/Umbraco.Web.BackOffice/EmbeddedResources/Tours/getting-started.json diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs index d63deda88a..35f41e0af7 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs @@ -44,9 +44,6 @@ namespace Umbraco.Extensions public static IApplicationBuilder UseUmbracoPreview(this IApplicationBuilder app) { - // TODO: I'm unsure this middleware will execute before the endpoint, we'll have to see - app.UseMiddleware(); - app.UseEndpoints(endpoints => { PreviewRoutes previewRoutes = app.ApplicationServices.GetRequiredService(); diff --git a/src/Umbraco.Web.BackOffice/PropertyEditors/RichTextPreValueController.cs b/src/Umbraco.Web.BackOffice/PropertyEditors/RichTextPreValueController.cs index faa4cc83dc..3d0d746d5a 100644 --- a/src/Umbraco.Web.BackOffice/PropertyEditors/RichTextPreValueController.cs +++ b/src/Umbraco.Web.BackOffice/PropertyEditors/RichTextPreValueController.cs @@ -1,13 +1,10 @@ -using System.Collections.Generic; -using System.Xml; +using System.Linq; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Cms.Web.Common.Attributes; -using Umbraco.Extensions; -using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Web.BackOffice.PropertyEditors { @@ -17,115 +14,35 @@ namespace Umbraco.Cms.Web.BackOffice.PropertyEditors [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] public class RichTextPreValueController : UmbracoAuthorizedJsonController { - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IOptions _richTextEditorSettings; - public RichTextPreValueController(IHostingEnvironment hostingEnvironment) + public RichTextPreValueController(IOptions richTextEditorSettings) { - _hostingEnvironment = hostingEnvironment; + _richTextEditorSettings = richTextEditorSettings; } - private static volatile bool _init; - private static readonly object Locker = new object(); - private static readonly Dictionary Commands = new Dictionary(); - private static readonly Dictionary Plugins = new Dictionary(); - private static readonly Dictionary ConfigOptions = new Dictionary(); - - private static string _invalidElements = ""; - private static string _validElements = ""; - public RichTextEditorConfiguration GetConfiguration() { - EnsureInit(); + var settings = _richTextEditorSettings.Value; var config = new RichTextEditorConfiguration { - Plugins = Plugins.Values, - Commands = Commands.Values, - ValidElements = _validElements, - InvalidElements = _invalidElements, - CustomConfig = ConfigOptions + Plugins = settings.Plugins.Select(x=>new RichTextEditorPlugin() + { + Name = x + }), + Commands = settings.Commands.Select(x=>new RichTextEditorCommand() + { + Alias = x.Alias, + Mode = x.Mode, + Name = x.Name + }), + ValidElements = settings.ValidElements, + InvalidElements = settings.InvalidElements, + CustomConfig = settings.CustomConfig }; return config; } - - private void EnsureInit() - { - - if (_init == false) - { - lock (Locker) - { - if (_init == false) - { - // Load config - XmlDocument xd = new XmlDocument(); - xd.Load(_hostingEnvironment.MapPathContentRoot(SystemFiles.TinyMceConfig)); - - foreach (XmlNode n in xd.DocumentElement.SelectNodes("//command")) - { - var alias = n.AttributeValue("alias").ToLower(); - - if (!Commands.ContainsKey(alias)) - Commands.Add( - alias, - new RichTextEditorCommand() - { - Name = n.AttributeValue("name") ?? alias, - Alias = alias, - Mode = Enum.Parse(n.AttributeValue("mode"), true) - } - ); - } - - - foreach (XmlNode n in xd.DocumentElement.SelectNodes("//plugin")) - { - if (!Plugins.ContainsKey(n.FirstChild.Value)) - { - - Plugins.Add( - n.FirstChild.Value.ToLower(), - new RichTextEditorPlugin() - { - Name = n.FirstChild.Value, - }); - } - } - - - foreach (XmlNode n in xd.DocumentElement.SelectNodes("//config")) - { - if (!ConfigOptions.ContainsKey(n.Attributes["key"].FirstChild.Value)) - { - var value = ""; - if (n.FirstChild != null) - value = n.FirstChild.Value; - - ConfigOptions.Add( - n.Attributes["key"].FirstChild.Value.ToLower(), - value); - } - } - - if (xd.DocumentElement.SelectSingleNode("./invalidElements") != null) - _invalidElements = xd.DocumentElement.SelectSingleNode("./invalidElements").FirstChild.Value; - if (xd.DocumentElement.SelectSingleNode("./validElements") != null) - { - string _val = xd.DocumentElement.SelectSingleNode("./validElements").FirstChild.Value.Replace("\r", "").Replace("\n", ""); - _validElements = _val; - - /*foreach (string s in _val.Split("\n".ToCharArray())) - _validElements += "'" + s + "' + \n"; - _validElements = _validElements.Substring(0, _validElements.Length - 4);*/ - } - - _init = true; - } - } - } - - } - } } diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj index 22799eaa63..d49eb6e4f5 100644 --- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj +++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj @@ -4,6 +4,9 @@ net5.0 Library Umbraco.Cms.Web.BackOffice + Umbraco.Cms.Web.BackOffice + Umbraco CMS Back Office + Contains the Back Office assembly needed to run the back office of Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco @@ -36,4 +39,8 @@ + + + + diff --git a/src/Umbraco.Web.Common/DependencyInjection/StaticServiceProvider.cs b/src/Umbraco.Web.Common/DependencyInjection/StaticServiceProvider.cs new file mode 100644 index 0000000000..c73685b41d --- /dev/null +++ b/src/Umbraco.Web.Common/DependencyInjection/StaticServiceProvider.cs @@ -0,0 +1,25 @@ +using System; +using System.ComponentModel; + +namespace Umbraco.Cms.Web.Common.DependencyInjection +{ + /// + /// INTERNAL Service locator. Should only be used if no other ways exist. + /// + /// + /// It is created with only two goals in mind + /// 1) Continue to have the same extension methods on IPublishedContent and IPublishedElement as in V8. To make migration easier. + /// 2) To have a tool to avoid breaking changes in minor versions. All methods using this should in theory be obsolete. + /// + /// Keep in mind, every time this is used, the code becomes basically untestable. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class StaticServiceProvider + { + /// + /// The service locator. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static IServiceProvider Instance { get; set; } + } +} diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 0d3211d90d..4b3715cd52 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -280,6 +280,7 @@ namespace Umbraco.Extensions builder.Services.AddUnique(); builder.Services.AddUnique(); + builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoStartupFilter.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoStartupFilter.cs index d843bfb4aa..f8ca73e283 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoStartupFilter.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoStartupFilter.cs @@ -19,6 +19,7 @@ namespace Umbraco.Cms.Web.Common.DependencyInjection public Action Configure(Action next) => app => { + StaticServiceProvider.Instance = app.ApplicationServices; _options.Value.PreUmbracoPipeline(app); app.UseUmbraco(); diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index 50dbe7a6ac..1036a1f630 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -126,6 +126,7 @@ namespace Umbraco.Extensions } else { + app.UseMiddleware(); app.UseMiddleware(); app.UseMiddleware(); } diff --git a/src/Umbraco.Web.Common/Extensions/FriendlyPublishedContentExtensions.cs b/src/Umbraco.Web.Common/Extensions/FriendlyPublishedContentExtensions.cs new file mode 100644 index 0000000000..b9cdddfdeb --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/FriendlyPublishedContentExtensions.cs @@ -0,0 +1,537 @@ +using System; +using System.Collections.Generic; +using System.Data; +using Examine; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.DependencyInjection; + +namespace Umbraco.Extensions +{ + public static class FriendlyPublishedContentExtensions + { + private static IVariationContextAccessor VariationContextAccessor { get; } = + StaticServiceProvider.Instance.GetRequiredService(); + + private static IPublishedModelFactory PublishedModelFactory { get; } = + StaticServiceProvider.Instance.GetRequiredService(); + + private static IPublishedUrlProvider PublishedUrlProvider { get; } = + StaticServiceProvider.Instance.GetRequiredService(); + + private static IUserService UserService { get; } = + StaticServiceProvider.Instance.GetRequiredService(); + + private static IUmbracoContextAccessor UmbracoContextAccessor { get; } = + StaticServiceProvider.Instance.GetRequiredService(); + + private static ISiteDomainHelper SiteDomainHelper { get; } = + StaticServiceProvider.Instance.GetRequiredService(); + + private static IExamineManager ExamineManager { get; } = + StaticServiceProvider.Instance.GetRequiredService(); + + private static IFileService FileService { get; } = + StaticServiceProvider.Instance.GetRequiredService(); + + private static IOptions WebRoutingSettings { get; } = + StaticServiceProvider.Instance.GetRequiredService>(); + + private static IContentTypeService ContentTypeService { get; } = + StaticServiceProvider.Instance.GetRequiredService(); + + private static IPublishedValueFallback PublishedValueFallback { get; } = + StaticServiceProvider.Instance.GetRequiredService(); + + private static IPublishedSnapshot PublishedSnapshot => UmbracoContextAccessor.UmbracoContext?.PublishedSnapshot; + + private static IMediaTypeService MediaTypeService { get; } = + StaticServiceProvider.Instance.GetRequiredService(); + + + private static IMemberTypeService MemberTypeService { get; } = + StaticServiceProvider.Instance.GetRequiredService(); + + + /// + /// Creates a strongly typed published content model for an internal published content. + /// + /// The internal published content. + /// The strongly typed published content model. + public static IPublishedContent CreateModel( + this IPublishedContent content) + => content.CreateModel(PublishedModelFactory); + + /// + /// Gets the name of the content item. + /// + /// The content item. + /// The specific culture to get the name for. If null is used the current culture is used (Default is null). + public static string Name( + this IPublishedContent content, + string culture = null) + => content.Name(VariationContextAccessor, culture); + + /// + /// Gets the URL segment of the content item. + /// + /// The content item. + /// The specific culture to get the URL segment for. If null is used the current culture is used (Default is null). + public static string UrlSegment( + this IPublishedContent content, + string culture = null) + => content.UrlSegment(VariationContextAccessor, culture); + + /// + /// Gets the culture date of the content item. + /// + /// The content item. + /// The specific culture to get the name for. If null is used the current culture is used (Default is null). + public static DateTime CultureDate( + this IPublishedContent content, + string culture = null) + => content.CultureDate(VariationContextAccessor, culture); + + /// + /// Returns the current template Alias + /// + /// Empty string if none is set. + public static string GetTemplateAlias(this IPublishedContent content) + => content.GetTemplateAlias(FileService); + + public static bool IsAllowedTemplate(this IPublishedContent content, int templateId) + => content.IsAllowedTemplate(ContentTypeService, WebRoutingSettings.Value, templateId); + + public static bool IsAllowedTemplate( + this IPublishedContent content, + bool disableAlternativeTemplates, + bool validateAlternativeTemplates, + int templateId) + => content.IsAllowedTemplate( + ContentTypeService, + disableAlternativeTemplates, + validateAlternativeTemplates, + templateId); + + public static bool IsAllowedTemplate( + this IPublishedContent content, + bool disableAlternativeTemplates, + bool validateAlternativeTemplates, + string templateAlias) + => content.IsAllowedTemplate( + FileService, + ContentTypeService, + disableAlternativeTemplates, + validateAlternativeTemplates, + templateAlias); + + + /// + /// Gets a value indicating whether the content has a value for a property identified by its alias. + /// + /// The content. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// A value indicating whether the content has a value for the property identified by the alias. + /// Returns true if HasValue is true, or a fallback strategy can provide a value. + public static bool HasValue( + this IPublishedContent content, + string alias, + string culture = null, + string segment = null, + Fallback fallback = default) + => + content.HasValue(PublishedValueFallback, alias, culture, segment, fallback); + + /// + /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. + /// + /// The content. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// The default value. + /// The value of the content's property identified by the alias, if it exists, otherwise a default value. + public static object Value(this IPublishedContent content, string alias, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default) + => content.Value(PublishedValueFallback, alias, culture, segment, fallback, defaultValue); + + /// + /// Gets the value of a content's property identified by its alias, converted to a specified type. + /// + /// The target property type. + /// The content. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// The default value. + /// The value of the content's property identified by the alias, converted to the specified type. + public static T Value(this IPublishedContent content, string alias, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default) + => content.Value(PublishedValueFallback, alias, culture, segment, fallback, defaultValue); + + /// + /// Returns all DescendantsOrSelf of all content referenced + /// + /// + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// + /// + /// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot + /// + public static IEnumerable DescendantsOrSelfOfType( + this IEnumerable parentNodes, string docTypeAlias, string culture = null) + => parentNodes.DescendantsOrSelfOfType(VariationContextAccessor, docTypeAlias, culture); + + /// + /// Returns all DescendantsOrSelf of all content referenced + /// + /// + /// Variation context accessor. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// + /// + /// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot + /// + public static IEnumerable DescendantsOrSelf( + this IEnumerable parentNodes, + string culture = null) + where T : class, IPublishedContent + => parentNodes.DescendantsOrSelf(VariationContextAccessor, culture); + + public static IEnumerable Descendants(this IPublishedContent content, string culture = null) + => content.Descendants(VariationContextAccessor, culture); + + public static IEnumerable Descendants(this IPublishedContent content, int level, string culture = null) + => content.Descendants(VariationContextAccessor, level, culture); + + public static IEnumerable DescendantsOfType(this IPublishedContent content, + string contentTypeAlias, string culture = null) + => content.DescendantsOfType(VariationContextAccessor, contentTypeAlias, culture); + + public static IEnumerable Descendants(this IPublishedContent content, string culture = null) + where T : class, IPublishedContent + => content.Descendants(VariationContextAccessor, culture); + + public static IEnumerable Descendants(this IPublishedContent content, int level, string culture = null) + where T : class, IPublishedContent + => content.Descendants(VariationContextAccessor, level, culture); + + public static IEnumerable DescendantsOrSelf(this IPublishedContent content, string culture = null) + => content.DescendantsOrSelf(VariationContextAccessor, culture); + + + public static IEnumerable DescendantsOrSelf(this IPublishedContent content, int level, string culture = null) + => content.DescendantsOrSelf(VariationContextAccessor, level, culture); + + public static IEnumerable DescendantsOrSelfOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) + => content.DescendantsOrSelfOfType(VariationContextAccessor, contentTypeAlias, culture); + + public static IEnumerable DescendantsOrSelf(this IPublishedContent content, string culture = null) + where T : class, IPublishedContent + => content.DescendantsOrSelf(VariationContextAccessor, culture); + + public static IEnumerable DescendantsOrSelf(this IPublishedContent content, int level, string culture = null) + where T : class, IPublishedContent + => content.DescendantsOrSelf(VariationContextAccessor, level, culture); + + public static IPublishedContent Descendant(this IPublishedContent content, string culture = null) + => content.Descendant(VariationContextAccessor, culture); + + public static IPublishedContent Descendant(this IPublishedContent content, int level, string culture = null) + => content.Descendant(VariationContextAccessor, level, culture); + + + public static IPublishedContent DescendantOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) + => content.DescendantOfType(VariationContextAccessor, contentTypeAlias, culture); + + public static T Descendant(this IPublishedContent content, string culture = null) + where T : class, IPublishedContent + => content.Descendant(VariationContextAccessor, culture); + + + public static T Descendant(this IPublishedContent content, int level, string culture = null) + where T : class, IPublishedContent + => content.Descendant(VariationContextAccessor, level, culture); + + public static IPublishedContent DescendantOrSelf(this IPublishedContent content, string culture = null) + => content.DescendantOrSelf(VariationContextAccessor, culture); + + public static IPublishedContent DescendantOrSelf(this IPublishedContent content, int level, string culture = null) + => content.DescendantOrSelf(VariationContextAccessor, level, culture); + + public static IPublishedContent DescendantOrSelfOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) + => content.DescendantOrSelfOfType(VariationContextAccessor, contentTypeAlias, culture); + + public static T DescendantOrSelf(this IPublishedContent content, string culture = null) + where T : class, IPublishedContent + => content.DescendantOrSelf(VariationContextAccessor, culture); + + public static T DescendantOrSelf(this IPublishedContent content, int level, string culture = null) + where T : class, IPublishedContent + => content.DescendantOrSelf(VariationContextAccessor, level, culture); + + + /// + /// Gets the children of the content item. + /// + /// The content item. + /// + /// The specific culture to get the URL children for. Default is null which will use the current culture in + /// + /// + /// Gets children that are available for the specified culture. + /// Children are sorted by their sortOrder. + /// + /// For culture, + /// if null is used the current culture is used. + /// If an empty string is used only invariant children are returned. + /// If "*" is used all children are returned. + /// + /// + /// If a variant culture is specified or there is a current culture in the then the Children returned + /// will include both the variant children matching the culture AND the invariant children because the invariant children flow with the current culture. + /// However, if an empty string is specified only invariant children are returned. + /// + /// + public static IEnumerable Children(this IPublishedContent content, string culture = null) + => content.Children(VariationContextAccessor, culture); + + /// + /// Gets the children of the content, filtered by a predicate. + /// + /// The content. + /// The predicate. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The children of the content, filtered by the predicate. + /// + /// Children are sorted by their sortOrder. + /// + public static IEnumerable Children(this IPublishedContent content, Func predicate, string culture = null) + => content.Children(VariationContextAccessor, predicate, culture); + + /// + /// Gets the children of the content, of any of the specified types. + /// + /// The content. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The content type alias. + /// The children of the content, of any of the specified types. + public static IEnumerable ChildrenOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) + => content.ChildrenOfType(VariationContextAccessor, contentTypeAlias, culture); + + /// + /// Gets the children of the content, of a given content type. + /// + /// The content type. + /// The content. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The children of content, of the given content type. + /// + /// Children are sorted by their sortOrder. + /// + public static IEnumerable Children(this IPublishedContent content, string culture = null) + where T : class, IPublishedContent + => content.Children(VariationContextAccessor, culture); + + public static IPublishedContent FirstChild(this IPublishedContent content, string culture = null) + => content.FirstChild(VariationContextAccessor, culture); + + /// + /// Gets the first child of the content, of a given content type. + /// + public static IPublishedContent FirstChildOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) + => content.FirstChildOfType(VariationContextAccessor, contentTypeAlias, culture); + + public static IPublishedContent FirstChild(this IPublishedContent content, Func predicate, string culture = null) + => content.FirstChild(VariationContextAccessor, predicate, culture); + + public static IPublishedContent FirstChild(this IPublishedContent content, Guid uniqueId, string culture = null) + => content.FirstChild(VariationContextAccessor, uniqueId, culture); + + + public static T FirstChild(this IPublishedContent content, string culture = null) + where T : class, IPublishedContent + => content.FirstChild(VariationContextAccessor, culture); + + public static T FirstChild(this IPublishedContent content, Func predicate, string culture = null) + where T : class, IPublishedContent + => content.FirstChild(VariationContextAccessor, predicate, culture); + + /// + /// Gets the siblings of the content. + /// + /// The content. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The siblings of the content. + /// + /// Note that in V7 this method also return the content node self. + /// + public static IEnumerable Siblings(this IPublishedContent content, string culture = null) + => content.Siblings(PublishedSnapshot, VariationContextAccessor, culture); + + /// + /// Gets the siblings of the content, of a given content type. + /// + /// The content. + /// The content type alias. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The siblings of the content, of the given content type. + /// + /// Note that in V7 this method also return the content node self. + /// + public static IEnumerable SiblingsOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) + => content.SiblingsOfType(PublishedSnapshot, VariationContextAccessor, contentTypeAlias, culture); + + /// + /// Gets the siblings of the content, of a given content type. + /// + /// The content type. + /// The content. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The siblings of the content, of the given content type. + /// + /// Note that in V7 this method also return the content node self. + /// + public static IEnumerable Siblings(this IPublishedContent content, string culture = null) + where T : class, IPublishedContent + => content.Siblings(PublishedSnapshot, VariationContextAccessor, culture); + + /// + /// Gets the siblings of the content including the node itself to indicate the position. + /// + /// The content. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The siblings of the content including the node itself. + public static IEnumerable SiblingsAndSelf(this IPublishedContent content, string culture = null) + => content.SiblingsAndSelf(PublishedSnapshot, VariationContextAccessor, culture); + + /// + /// Gets the siblings of the content including the node itself to indicate the position, of a given content type. + /// + /// The content. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The content type alias. + /// The siblings of the content including the node itself, of the given content type. + public static IEnumerable SiblingsAndSelfOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) + => content.SiblingsAndSelfOfType(PublishedSnapshot, VariationContextAccessor, contentTypeAlias, culture); + + /// + /// Gets the siblings of the content including the node itself to indicate the position, of a given content type. + /// + /// The content type. + /// The content. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The siblings of the content including the node itself, of the given content type. + public static IEnumerable SiblingsAndSelf(this IPublishedContent content, string culture = null) + where T : class, IPublishedContent + => content.SiblingsAndSelf(PublishedSnapshot, VariationContextAccessor, culture); + + + /// + /// Gets the url of the content item. + /// + /// + /// If the content item is a document, then this method returns the url of the + /// document. If it is a media, then this methods return the media url for the + /// 'umbracoFile' property. Use the MediaUrl() method to get the media url for other + /// properties. + /// The value of this property is contextual. It depends on the 'current' request uri, + /// if any. In addition, when the content type is multi-lingual, this is the url for the + /// specified culture. Otherwise, it is the invariant url. + /// + public static string Url(this IPublishedContent content, string culture = null, UrlMode mode = UrlMode.Default) + => content.Url(PublishedUrlProvider, culture, mode); + + /// + /// Gets the children of the content in a DataTable. + /// + /// The content. + /// Variation context accessor. + /// The content type service. + /// The media type service. + /// The member type service. + /// The published url provider. + /// An optional content type alias. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The children of the content. + public static DataTable ChildrenAsTable(this IPublishedContent content, string contentTypeAliasFilter = "", string culture = null) + => content.ChildrenAsTable(VariationContextAccessor, ContentTypeService, MediaTypeService, MemberTypeService, PublishedUrlProvider, contentTypeAliasFilter, culture); + + /// + /// Gets the url for a media. + /// + /// The content item. + /// The culture (use current culture by default). + /// The url mode (use site configuration by default). + /// The alias of the property (use 'umbracoFile' by default). + /// The url for the media. + /// + /// The value of this property is contextual. It depends on the 'current' request uri, + /// if any. In addition, when the content type is multi-lingual, this is the url for the + /// specified culture. Otherwise, it is the invariant url. + /// + public static string MediaUrl( + this IPublishedContent content, + string culture = null, + UrlMode mode = UrlMode.Default, + string propertyAlias = Constants.Conventions.Media.File) + => content.MediaUrl(PublishedUrlProvider, culture, mode, propertyAlias); + + /// + /// Gets the name of the content item creator. + /// + /// The content item. + public static string CreatorName(this IPublishedContent content) => + content.CreatorName(UserService); + + /// + /// Gets the name of the content item writer. + /// + /// The content item. + public static string WriterName(this IPublishedContent content) => + content.WriterName(UserService); + + /// + /// Gets the culture assigned to a document by domains, in the context of a current Uri. + /// + /// The document. + /// An optional current Uri. + /// The culture assigned to the document by domains. + /// + /// In 1:1 multilingual setup, a document contains several cultures (there is not + /// one document per culture), and domains, withing the context of a current Uri, assign + /// a culture to that document. + /// + public static string GetCultureFromDomains( + this IPublishedContent content, + Uri current = null) + => content.GetCultureFromDomains(UmbracoContextAccessor, SiteDomainHelper, current); + + + public static IEnumerable SearchDescendants( + this IPublishedContent content, + string term, + string indexName = null) + => content.SearchDescendants(ExamineManager, UmbracoContextAccessor, term, indexName); + + + public static IEnumerable SearchChildren( + this IPublishedContent content, + string term, + string indexName = null) + => content.SearchChildren(ExamineManager, UmbracoContextAccessor, term, indexName); + + + } +} diff --git a/src/Umbraco.Web.Common/Extensions/FriendlyPublishedElementExtensions.cs b/src/Umbraco.Web.Common/Extensions/FriendlyPublishedElementExtensions.cs new file mode 100644 index 0000000000..bbb896fe8b --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/FriendlyPublishedElementExtensions.cs @@ -0,0 +1,74 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Web.Common.DependencyInjection; + +namespace Umbraco.Extensions +{ + public static class FriendlyPublishedElementExtensions + { + private static IPublishedValueFallback PublishedValueFallback { get; } = + StaticServiceProvider.Instance.GetRequiredService(); + + /// + /// Gets the value of a content's property identified by its alias. + /// + /// The content. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// The default value. + /// The value of the content's property identified by the alias, if it exists, otherwise a default value. + /// + /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. + /// If no property with the specified alias exists, or if the property has no value, returns . + /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. + /// The alias is case-insensitive. + /// + public static object Value( + this IPublishedElement content, + string alias, + string culture = null, + string segment = null, + Fallback fallback = default, + object defaultValue = default) + => content.Value(PublishedValueFallback, alias, culture, segment, fallback, defaultValue); + + /// + /// Gets the value of a content's property identified by its alias, converted to a specified type. + /// + /// The target property type. + /// The content. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// The default value. + /// The value of the content's property identified by the alias, converted to the specified type. + /// + /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. + /// If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns default(T). + /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. + /// The alias is case-insensitive. + /// + public static T Value( + this IPublishedElement content, + string alias, + string culture = null, + string segment = null, + Fallback fallback = default, + T defaultValue = default) + => content.Value(PublishedValueFallback, alias, culture, segment, fallback, defaultValue); + + /// + /// Gets a value indicating whether the content is visible. + /// + /// The content. + /// A value indicating whether the content is visible. + /// A content is not visible if it has an umbracoNaviHide property with a value of "1". Otherwise, + /// the content is visible. + public static bool IsVisible(this IPublishedElement content) => content.IsVisible(PublishedValueFallback); + + + } +} diff --git a/src/Umbraco.Web.Website/Extensions/PublishedContentExtensions.cs b/src/Umbraco.Web.Common/Extensions/PublishedContentExtensions.cs similarity index 99% rename from src/Umbraco.Web.Website/Extensions/PublishedContentExtensions.cs rename to src/Umbraco.Web.Common/Extensions/PublishedContentExtensions.cs index c33d5e6ef3..c02e609372 100644 --- a/src/Umbraco.Web.Website/Extensions/PublishedContentExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/PublishedContentExtensions.cs @@ -3,12 +3,12 @@ using System.Collections.Generic; using System.Web; using Examine; using Microsoft.AspNetCore.Html; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Infrastructure.Examine; -using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Extensions { diff --git a/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs similarity index 53% rename from src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs rename to src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs index b03769d28b..50338f70e3 100644 --- a/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs @@ -3,35 +3,47 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Extensions; -using Constants = Umbraco.Cms.Core.Constants; -namespace Umbraco.Cms.Web.BackOffice.Middleware +namespace Umbraco.Cms.Web.Common.Middleware { /// /// Ensures that preview pages (front-end routed) are authenticated with the back office identity appended to the principal alongside any default authentication that takes place /// public class PreviewAuthenticationMiddleware : IMiddleware { + private readonly ILogger _logger; + + public PreviewAuthenticationMiddleware(ILogger logger) => _logger = logger; + /// public async Task InvokeAsync(HttpContext context, RequestDelegate next) { var request = context.Request; - if (!request.IsClientSideRequest()) + + // do not process if client-side request + if (request.IsClientSideRequest()) + { + await next(context); + return; + } + + try { var isPreview = request.HasPreviewCookie() - && context.User != null - && !request.IsBackOfficeRequest(); + && context.User != null + && !request.IsBackOfficeRequest(); if (isPreview) { var cookieOptions = context.RequestServices.GetRequiredService>() - .Get(Constants.Security.BackOfficeAuthenticationType); + .Get(Core.Constants.Security.BackOfficeAuthenticationType); if (cookieOptions == null) { - throw new InvalidOperationException("No cookie options found with name " + Constants.Security.BackOfficeAuthenticationType); + throw new InvalidOperationException("No cookie options found with name " + Core.Constants.Security.BackOfficeAuthenticationType); } // If we've gotten this far it means a preview cookie has been set and a front-end umbraco document request is executing. @@ -40,23 +52,27 @@ namespace Umbraco.Cms.Web.BackOffice.Middleware if (request.Cookies.TryGetValue(cookieOptions.Cookie.Name, out var cookie)) { var unprotected = cookieOptions.TicketDataFormat.Unprotect(cookie); - if (unprotected != null) + var backOfficeIdentity = unprotected?.Principal.GetUmbracoIdentity(); + if (backOfficeIdentity != null) { - var backOfficeIdentity = unprotected.Principal.GetUmbracoIdentity(); - if (backOfficeIdentity != null) - { - // Ok, we've got a real ticket, now we can add this ticket's identity to the current - // Principal, this means we'll have 2 identities assigned to the principal which we can - // use to authorize the preview and allow for a back office User. - context.User.AddIdentity(backOfficeIdentity); - } + // Ok, we've got a real ticket, now we can add this ticket's identity to the current + // Principal, this means we'll have 2 identities assigned to the principal which we can + // use to authorize the preview and allow for a back office User. + context.User.AddIdentity(backOfficeIdentity); } } } } - - await next(context); + catch (Exception ex) + { + // log any errors and continue the request without preview + _logger.LogError($"Unable to perform preview authentication: {ex.Message}"); + } + finally + { + await next(context); + } } } } diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index bb302dbbc0..dce3ad7336 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -4,6 +4,9 @@ net5.0 Library Umbraco.Cms.Web.Common + Umbraco.Cms.Web.Common + Umbraco CMS Web + Contains the Web assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco diff --git a/src/Umbraco.Web.UI.Client/src/common/services/templatehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/templatehelper.service.js index 2e71ef0bf5..1a2f0735ce 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/templatehelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/templatehelper.service.js @@ -25,7 +25,7 @@ "\t@foreach (var item in selection)\n" + "\t{\n" + "\t\t
  • \n" + - "\t\t\t@item.Name\n" + + "\t\t\t@item.Name()\n" + "\t\t
  • \n" + "\t}\n" + "\n\n"; @@ -43,11 +43,11 @@ function getAddSectionSnippet(sectionName) { return "@section " + sectionName + "\r\n{\r\n\r\n\t{0}\r\n\r\n}\r\n"; } - + function getGeneralShortcuts(){ var keys = [ - "shortcuts_generalHeader", - "buttons_undo", + "shortcuts_generalHeader", + "buttons_undo", "buttons_redo", "buttons_save" ]; @@ -61,7 +61,7 @@ labels.save = data[3]; return { - "name": labels.header, + "name": labels.header, "shortcuts": [ { "description": labels.undo, @@ -77,14 +77,14 @@ } ] }; - }); + }); } function getEditorShortcuts(){ var keys = [ - "shortcuts_editorHeader", - "shortcuts_commentLine", + "shortcuts_editorHeader", + "shortcuts_commentLine", "shortcuts_removeLine", "shortcuts_copyLineUp", "shortcuts_copyLineDown", @@ -126,7 +126,7 @@ "keys": { "win": [{ "key": "alt" }, { "key": "shift" }, { "key": "down" }], "mac": [{ "key": "cmd" }, { "key": "alt" }, { "key": "down" }] - } + } }, { "description": labels.movelineup, @@ -138,13 +138,13 @@ } ] }; - }); + }); } function getTemplateEditorShortcuts(){ var keys = [ - "template_insert", - "template_insertPageField", + "template_insert", + "template_insertPageField", "template_insertPartialView", "template_insertDictionaryItem", "template_insertMacro", @@ -198,13 +198,13 @@ } ] }; - }); + }); } function getPartialViewEditorShortcuts(){ var keys = [ - "template_insert", - "template_insertPageField", + "template_insert", + "template_insertPageField", "template_insertDictionaryItem", "template_insertMacro", "template_queryBuilder" @@ -242,7 +242,7 @@ }; }); - + } //////////// diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/template-helper.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/template-helper.spec.js index 08a69b7e80..316cfa7c59 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/template-helper.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/template-helper.spec.js @@ -50,7 +50,7 @@ describe('service: templateHelper', function () { var snippet = '@Html.Partial("Folder with spaces/Footer")'; expect(templateHelper.getInsertPartialSnippet(parentId, nodeName)).toBe(snippet); }); - + }); describe('getQuerySnippet', function () { @@ -62,14 +62,14 @@ describe('service: templateHelper', function () { "\t@foreach (var item in selection)\n" + "\t{\n" + "\t\t
  • \n" + - "\t\t\t@item.Name\n" + + "\t\t\t@item.Name()\n" + "\t\t
  • \n" + "\t}\n" + "\n\n"; expect(templateHelper.getQuerySnippet(queryExpression)).toBe(snippet); }); - + }); describe('getRenderBodySnippet', function () { @@ -78,7 +78,7 @@ describe('service: templateHelper', function () { var snippet = '@RenderBody()'; expect(templateHelper.getRenderBodySnippet()).toBe(snippet); }); - + }); describe('getRenderSectionSnippet', function () { @@ -92,7 +92,7 @@ describe('service: templateHelper', function () { var snippet = '@RenderSection("sectionName", true)'; expect(templateHelper.getRenderSectionSnippet("sectionName", true)).toBe(snippet); }); - + }); describe('getAddSectionSnippet', function () { @@ -102,7 +102,7 @@ describe('service: templateHelper', function () { var snippet = "@section " + sectionName + "\r\n{\r\n\r\n\t{0}\r\n\r\n}\r\n"; expect(templateHelper.getAddSectionSnippet(sectionName)).toEqual(snippet); }); - + }); -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.NetCore/appsettings.Development.json b/src/Umbraco.Web.UI.NetCore/appsettings.Development.json index 983b157ef1..06afcf2a7a 100644 --- a/src/Umbraco.Web.UI.NetCore/appsettings.Development.json +++ b/src/Umbraco.Web.UI.NetCore/appsettings.Development.json @@ -18,16 +18,29 @@ }, "Umbraco": { "CMS": { - "Global": { - "Smtp": { -// "From": "your@email.here", -// "Host": "localhost", -// "Port": "25" - } - }, - "Hosting": { - "Debug": true - } + "Global": { + "Smtp": { + //"From": "your@email.here", + //"Host": "localhost", + // "Port": "25" + } + }, + "Hosting": { + "Debug": true + }, + "RichTextEditor": { + "Commands" : [ + { + "Alias": "fullscreen", + "Name": "Full Screen", + "Mode": "All" + } + ], + "Plugins": [ + "fullscreen" + ] + } } + } } diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/cs-CZ.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/cs-CZ.user.xml deleted file mode 100644 index d4902d563d..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/cs-CZ.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/da-DK.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/da-DK.user.xml deleted file mode 100644 index 7a8ce2c28a..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/da-DK.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/de-DE.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/de-DE.user.xml deleted file mode 100644 index 7a8ce2c28a..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/de-DE.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/en-GB.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/en-GB.user.xml deleted file mode 100644 index 7a8ce2c28a..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/en-GB.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/en-US.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/en-US.user.xml deleted file mode 100644 index 7a8ce2c28a..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/en-US.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/es-ES.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/es-ES.user.xml deleted file mode 100644 index 7a8ce2c28a..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/es-ES.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/fr-FR.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/fr-FR.user.xml deleted file mode 100644 index 7a8ce2c28a..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/fr-FR.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/he-IL.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/he-IL.user.xml deleted file mode 100644 index 3a0ad355c3..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/he-IL.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/it-IT.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/it-IT.user.xml deleted file mode 100644 index 3a0ad355c3..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/it-IT.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/ja-JP.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/ja-JP.user.xml deleted file mode 100644 index 7a8ce2c28a..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/ja-JP.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/ko-KR.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/ko-KR.user.xml deleted file mode 100644 index 3a0ad355c3..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/ko-KR.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/nb-NO.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/nb-NO.user.xml deleted file mode 100644 index 3a0ad355c3..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/nb-NO.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/nl-NL.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/nl-NL.user.xml deleted file mode 100644 index 7a8ce2c28a..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/nl-NL.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/pl-PL.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/pl-PL.user.xml deleted file mode 100644 index 3a0ad355c3..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/pl-PL.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/pt-BR.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/pt-BR.user.xml deleted file mode 100644 index 3a0ad355c3..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/pt-BR.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/ru-RU.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/ru-RU.user.xml deleted file mode 100644 index 7a8ce2c28a..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/ru-RU.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/sv-SE.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/sv-SE.user.xml deleted file mode 100644 index 3a0ad355c3..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/sv-SE.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Umbraco.Web.UI.NetCore/config/lang/zh-CN.user.xml b/src/Umbraco.Web.UI.NetCore/config/lang/zh-CN.user.xml deleted file mode 100644 index 8d2add98dd..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/lang/zh-CN.user.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.NetCore/config/logviewer.searches.config.js b/src/Umbraco.Web.UI.NetCore/config/logviewer.searches.config.js deleted file mode 100644 index 345fe23764..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/logviewer.searches.config.js +++ /dev/null @@ -1,42 +0,0 @@ -[ - { - "name": "Find all logs where the Level is NOT Verbose and NOT Debug", - "query": "Not(@Level='Verbose') and Not(@Level='Debug')" - }, - { - "name": "Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)", - "query": "Has(@Exception)" - }, - { - "name": "Find all logs that have the property 'Duration'", - "query": "Has(Duration)" - }, - { - "name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms", - "query": "Has(Duration) and Duration > 1000" - }, - { - "name": "Find all logs that are from the namespace 'Umbraco.Core'", - "query": "StartsWith(SourceContext, 'Umbraco.Core')" - }, - { - "name": "Find all logs that use a specific log message template", - "query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'" - }, - { - "name": "Find logs where one of the items in the SortedComponentTypes property array is equal to", - "query": "SortedComponentTypes[?] = 'Umbraco.Web.Search.ExamineComponent'" - }, - { - "name": "Find logs where one of the items in the SortedComponentTypes property array contains", - "query": "Contains(SortedComponentTypes[?], 'DatabaseServer')" - }, - { - "name": "Find all logs that the message has localhost in it with SQL like", - "query": "@Message like '%localhost%'" - }, - { - "name": "Find all logs that the message that starts with 'end' in it with SQL like", - "query": "@Message like 'end%'" - } -] diff --git a/src/Umbraco.Web.UI.NetCore/config/tinyMceConfig.Release.config b/src/Umbraco.Web.UI.NetCore/config/tinyMceConfig.Release.config deleted file mode 100644 index f6a26ee89a..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/tinyMceConfig.Release.config +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - paste - anchor - charmap - table - lists - advlist - hr - autolink - directionality - tabfocus - searchreplace - - - - - font - - - - - raw - - diff --git a/src/Umbraco.Web.UI.NetCore/config/tinyMceConfig.config b/src/Umbraco.Web.UI.NetCore/config/tinyMceConfig.config deleted file mode 100644 index 7f7cb657e6..0000000000 --- a/src/Umbraco.Web.UI.NetCore/config/tinyMceConfig.config +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - paste - anchor - charmap - table - lists - advlist - hr - autolink - directionality - tabfocus - searchreplace - fullscreen - - - - - font - - - - - raw - - diff --git a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj index c38670f45d..a7c5e7a277 100644 --- a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj +++ b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj @@ -4,6 +4,9 @@ net5.0 Library Umbraco.Cms.Web.Website + Umbraco.Cms.Web.Website + Umbraco CMS Website + Contains the Website assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco
    diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5bb4ab72d3..b06e6446c2 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -105,6 +105,10 @@ all + + 1.0.5 + all + diff --git a/src/umbraco.sln b/src/umbraco.sln index 840c7213e6..0041474014 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -37,10 +37,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B5BD12C1 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuSpecs", "NuSpecs", "{227C3B55-80E5-4E7E-A802-BE16C5128B9D}" ProjectSection(SolutionItems) = preProject - ..\build\NuSpecs\UmbracoCms.Core.nuspec = ..\build\NuSpecs\UmbracoCms.Core.nuspec ..\build\NuSpecs\UmbracoCms.nuspec = ..\build\NuSpecs\UmbracoCms.nuspec ..\build\NuSpecs\UmbracoCms.SqlCe.nuspec = ..\build\NuSpecs\UmbracoCms.SqlCe.nuspec - ..\build\NuSpecs\UmbracoCms.Web.nuspec = ..\build\NuSpecs\UmbracoCms.Web.nuspec + ..\build\NuSpecs\UmbracoCms.Examine.Lucene.nuspec = ..\build\NuSpecs\UmbracoCms.Examine.Lucene.nuspec EndProjectSection EndProject Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Web.UI.Client", "http://localhost:3961", "{3819A550-DCEC-4153-91B4-8BA9F7F0B9B4}"