Compare commits

...

102 Commits

Author SHA1 Message Date
Sebastiaan Janssen 44cb5fd96b Apparently the highest minClientVersion allowed is 4.1.0 for now - see https://github.com/NuGet/NuGetGallery/blob/master/src/NuGetGallery/GalleryConstants.cs#L36 2019-03-19 09:24:41 +01:00
Robert b0b987f9cd Merge pull request #4710 from umbraco/temp8-4708-fix-for-exit-preview-with-null-culture
Fix for exit preview with null culture

(cherry picked from commit 9b25cdaa14)
2019-03-14 13:21:28 +01:00
Sebastiaan Janssen 3961e2533d Fix JS unit tests 2019-03-11 15:50:16 +01:00
Stephan b46f842400 Do not inject IEnumerable but Collection in UmbracoContextFactory
(cherry picked from commit 7e67bbea13)
2019-03-11 15:41:40 +01:00
Kenn Jacobsen 930acfeb68 Show the content app for media folders with content properties
(cherry picked from commit 9062b400d4)
2019-03-11 14:58:13 +01:00
Shannon b004c616ad Removes faulty CustomRouteUrlProvider
(cherry picked from commit 52beb01110)
2019-03-11 14:54:43 +01:00
Sebastiaan Janssen 9d64c4e551 Bugfix CoreRuntime
(cherry picked from commit 2209b37a4a and 7f31ddc250)
2019-03-11 14:45:48 +01:00
Sebastiaan Janssen 9a0aacd45a Upgrade to ModelsBuilder 8.0.2, support Custom Tool (#4881)
* Upgrade to ModelsBuilder 8.0.2, support Custom Tool

* Upgrade to ModelsBuilder 8.0.3

* Fix controllers

* Upgrade MB

* Fix previous commit

(cherry picked from commit c85c055424)
2019-03-11 14:37:59 +01:00
Kenn Jacobsen 29ff7bc340 Handle checkbox value conversion from Nested Content
(cherry picked from commit 2352b50d97)
2019-03-08 15:09:54 +01:00
Shannon 39a97fb768 Enable adding api controllers 2019-03-07 19:09:13 +11:00
Kenn Jacobsen 0852bd90d0 Always update URLs on save no matter which app is active
(cherry picked from commit 869d088e4b)
2019-03-06 17:26:36 +01:00
Niels Lyngsø e28365a0e5 hack for not using scope() in grid redordering
(cherry picked from commit a67163fd03)
2019-03-06 16:58:57 +01:00
Sebastiaan Janssen 0d16c79d0c Require higher NuGet version so we don't target VS15 any more (unsupported)
Making backups before we upgrade is no longer needed, removed from install.ps1
If people include the bin folder in their solution they will get errors upgrading now, we shouldn't need to try and fix this, there's no reason to include the bin folder in the solution - therefore install.core.ps1 is removed

(cherry picked from commit 82b78c7530)
2019-03-06 14:57:53 +01:00
Sebastiaan Janssen 7cc8983a25 Fix NuCache url segments
(cherry picked from commits 7a0f11bc6f and cfa94b1bb8)
2019-03-06 10:25:21 +01:00
Shannon e7f8baeca4 fixed correct destination url instead of #. 2019-03-05 16:14:06 +11:00
Stephan e716d14a0a Fix distributed cache handler for medias (#4751)
(cherry picked from commit 31b293aa4d)
2019-03-04 16:51:15 +01:00
Robert Foster 4c6a742007 Changed all references of umbracoUseSSL to Umbraco.Core.UseHttps to fix issue #4749
(cherry picked from commit 5aa38c1f43)
2019-03-04 15:33:08 +01:00
Sebastiaan Janssen 410241b61d 4766 fixed up other instances where nodeSelectHandler was being passed incorrect number of arguments
(cherry picked from commit 553bf6d304 and 4503967778)
2019-03-04 15:13:30 +01:00
Kenn Jacobsen 4e47fa9a70 Fix the "selected" checkmark in tree picker search results
(cherry picked from commit 09dc908332)
2019-03-04 15:10:55 +01:00
Robert Foster 9c5fb1fead Fix for Content App badges not being displayed (#4787)
(cherry picked from commit 055f983cbf)
2019-03-04 15:10:09 +01:00
Sebastiaan Janssen dc41db5fc1 Bumps version to 8.0.1 2019-03-04 15:08:23 +01:00
Sebastiaan Janssen 11b6a98df8 Bugfix exception during install, live models collision
(cherry picked from commit aa86dfa956)
2019-03-04 15:05:17 +01:00
Sebastiaan Janssen da9fc1e147 Make it possible to upload the same file twice or more (#4755)
(cherry picked from commit b5bda772af)
2019-03-04 15:04:27 +01:00
Sebastiaan Janssen 319c75757a Bugfix: Don't use ToString :'(
(cherry picked from commit ed6d54e64a)
2019-03-04 15:01:54 +01:00
Pete Duncanson 6e81e57c8e Removed System.Web.WebPages reference in nonodes.aspx as was failing a clean build
(cherry picked from commit f1d1c95709)
2019-03-04 14:58:05 +01:00
Sebastiaan Janssen 9ebe282ba6 Ensure we dont double insert
(cherry picked from commit 578eb8b32c and b86e7dc0e8)
2019-03-04 14:57:13 +01:00
Claus b8db430615 Revert "Fix UmbracoDatabaseFactory"
This reverts commit 716fc582a0.
2019-02-21 21:01:24 +01:00
Sebastiaan Janssen f9d51ba415 fix version 2019-02-21 19:32:11 +01:00
Stephan e07a17db29 Merge branch 'dev-v8' of https://github.com/umbraco/Umbraco-CMS into dev-v8 2019-02-21 19:17:01 +01:00
Stephan ad62983327 Introduce ILivePublishedModelFactory 2019-02-21 19:15:35 +01:00
Bjarke Berg fc8ae1b228 Merge remote-tracking branch 'origin/dev-v8' into dev-v8-fix 2019-02-21 16:24:31 +01:00
Stephan 48f27afbc9 Merge branch 'dev-v8' of https://github.com/umbraco/Umbraco-CMS into dev-v8 2019-02-21 16:23:20 +01:00
Stephan e1532f9215 Merge branch 'temp8-saving-event-cannot-publish-values' into dev-v8 2019-02-21 16:22:44 +01:00
Bjarke Berg a2279d03f6 Materializing the TypeContentSources before iterating them. 2019-02-21 16:13:21 +01:00
Bjarke Berg e52ff0ea03 merge fix 2019-02-21 14:57:41 +01:00
Bjarke Berg aaa8e881f8 Version number revert 2019-02-21 14:45:12 +01:00
Bjarke Berg 14ec6b8792 Merge branch 'dev-v8' into dev-v8-fix
# Conflicts:
#	src/Umbraco.Core/IO/ShadowWrapper.cs
2019-02-21 14:43:18 +01:00
Stephan 90feb463aa Cleanup 2019-02-21 14:13:37 +01:00
Stephan 73c4e734b3 Move to UniqueMediaPathScheme 2019-02-21 13:48:33 +01:00
Stephan c85b90d65d Combined Guids media path scheme 2019-02-21 13:48:14 +01:00
Stephan e923b940f9 Cleanup Url token encoding 2019-02-21 13:47:10 +01:00
Bjarke Berg d29d629eae Merge pull request #4688 from umbraco/temp8-4674-media-paths
Fix media paths length issues
2019-02-21 13:21:39 +01:00
Stephan 50b076ce38 Merge branch 'dev-v8' into temp8-saving-event-cannot-publish-values 2019-02-21 13:03:36 +01:00
Stephan badb55476b Merge branch 'dev-v8' of https://github.com/umbraco/Umbraco-CMS into dev-v8 2019-02-21 13:03:19 +01:00
Stephan 9ecd63eaed Move to UniqueMediaPathScheme 2019-02-21 12:20:10 +01:00
Stephan 5edd61107f Combined Guids media path scheme 2019-02-21 11:28:51 +01:00
Stephan c21039f1af Fix SqlServer bulk insert / unwrapping commands 2019-02-21 11:17:41 +01:00
Stephan 51f6c639bf Cleanup Url token encoding 2019-02-21 09:35:38 +01:00
Shannon d9a3fa443d fixes test 2019-02-21 15:29:36 +11:00
Shannon 13a1c02507 Fixes content service Saving events so that they are fired before the model's values are published when the service is publishing 2019-02-21 14:46:14 +11:00
Shannon 7a9bc19ffd Adds notes + unit test showing the problem 2019-02-21 12:08:00 +11:00
Stephan 3dfe653ef4 Better throwing on long path in filesystems 2019-02-20 17:04:29 +01:00
Stephan db4dc9006a Shorten filepaths 2019-02-20 17:04:29 +01:00
Stephan 212244b9f2 Hopefully fix PublishedMember 2019-02-20 17:04:29 +01:00
Stephan 12f7492d80 Reorg code 2019-02-20 17:04:29 +01:00
Claus d9d68cf6bf The content service is not returning the invalid properties when publishing fails
(cherry picked from commit d94e383ed7)
2019-02-20 16:17:39 +01:00
Claus d94e383ed7 The content service is not returning the invalid properties when publishing fails 2019-02-20 16:05:42 +01:00
Stephan 716fc582a0 Fix UmbracoDatabaseFactory 2019-02-20 15:52:21 +01:00
Stephan 50c9625cc6 Oops fix previous commit 2019-02-20 15:01:32 +01:00
Stephan 5cbc428590 Fix UserRepository 2019-02-20 15:00:23 +01:00
Stephan db4d88b2dc Shorten shadow filesystems identifiers 2019-02-20 14:10:15 +01:00
Stephan d9d54f7982 Merge branch 'temp8-fixes-published-content-children-method' into dev-v8
(cherry picked from commit 735d759208)
2019-02-20 13:36:10 +01:00
Shannon a61a2b4d97 Removes notes
(cherry picked from commit 717efb6b09)
2019-02-20 13:35:50 +01:00
Stephan 735d759208 Merge branch 'temp8-fixes-published-content-children-method' into dev-v8 2019-02-20 12:56:14 +01:00
Shannon 65f99a9bfd renames the method params to be consistent 2019-02-20 21:06:36 +11:00
Shannon 6c23066fb7 Fixes the Content.Children conundrum with ChildrenOfType method 2019-02-20 21:03:36 +11:00
Shannon 717efb6b09 Removes notes 2019-02-20 20:37:10 +11:00
Sebastiaan Janssen 91c52cffc8 And revert again 2019-02-19 15:57:40 +01:00
Sebastiaan Janssen 03e7779afb Update install screen 2019-02-19 15:57:06 +01:00
Sebastiaan Janssen 15562ec12f Bump version 2019-02-19 15:01:26 +01:00
Sebastiaan Janssen 6e8862954a Bump version 2019-02-19 15:00:58 +01:00
Warren Buckley 9c7ab96c92 Typo FilderedControllerFactory (#4650) 2019-02-19 14:23:26 +01:00
Stephan 49ca4bbc91 Fix MB in nuspec 2019-02-19 09:42:42 +01:00
Stephan 5121142760 Merge remote-tracking branch 'origin/temp8' into temp8 2019-02-19 08:48:59 +01:00
Stephan 4c53604bd0 Merge branch 'temp8-fixes-publishing-new-content' into temp8 2019-02-19 08:48:28 +01:00
Stephan bf329aa431 Merge branch 'temp8' into temp8-fixes-publishing-new-content 2019-02-19 08:45:54 +01:00
Bjarke Berg d456ee9a31 Merge pull request #4632 from umbraco/temp8-4438
Fixes 4438: Local variant picker squeezes the width of node-name-input, to make it scroll instead of disappear behind.
2019-02-19 08:36:33 +01:00
Stephan f06d720dbd Fix SqlServer detection 2019-02-19 08:12:48 +01:00
Bjarke Berg 87839f4f6a Merge pull request #4643 from umbraco/temp8-show-notification-msg-client-validation
v8 - Shows a notification message when client validation fails in the content editor, else it's not clear there are any problems
2019-02-19 07:54:55 +01:00
Shannon 02f8c4a106 Adds TODO note 2019-02-19 16:55:57 +11:00
Shannon 71ad7df787 adds code comments 2019-02-19 12:23:08 +11:00
Shannon 4b7520b6f8 Updates the validation warning for media and members 2019-02-19 12:06:55 +11:00
Shannon c06c996ccf Shows a notification message when client validation fails in the content editor, else it's not clear there are any problems. 2019-02-19 11:46:46 +11:00
Sebastiaan Janssen 9fe4d9c44e Make "add group" shortcut consistent 2019-02-18 22:00:25 +01:00
Carole Rennie Logan 560174a21f V8: Changing tab to group (#4391) 2019-02-18 21:44:32 +01:00
Kenn Jacobsen e34a0ec73b Update/fix the blur handling for the tags editor 2019-02-18 20:54:55 +01:00
Jan Skovgaard 152b318d78 v8: Remove open sans font (#4613) 2019-02-18 20:52:18 +01:00
BatJan 414c6b8788 Remove old preview functionality 2019-02-18 20:50:57 +01:00
Kenn Jacobsen 19fb1a55e3 Add missing dirty checks to mediatypes 2019-02-18 20:34:16 +01:00
Kenn Jacobsen b02a9c3e7a Add missing dirty checks to content type dirty checking 2019-02-18 20:22:19 +01:00
Shannon e6f8309d54 reverts the list view changes so they open normally 2019-02-18 17:01:34 +01:00
Shannon 3ecf4144e7 Fixes publishing new variant content that has validation errors 2019-02-19 02:51:05 +11:00
Sebastiaan Janssen 817dd21155 Allow for unselecting a color in the color picker 2019-02-18 16:42:05 +01:00
Sebastiaan Janssen 34216fd635 Create a valid date picker datatype 2019-02-18 16:16:31 +01:00
Shannon c81ff85b41 Merge branch 'kjac-v8-fix-dont-retain-cculture' into temp8 2019-02-19 01:52:26 +11:00
Shannon ca1ec0e4a3 Merge branch 'v8-fix-dont-retain-cculture' of https://github.com/kjac/Umbraco-CMS into kjac-v8-fix-dont-retain-cculture 2019-02-19 01:49:27 +11:00
Shannon f2ae41ada2 Fixes the culturerequest.interceptor so that it works in tests (and so circular deps aren't caused), internalizes ClientCulture and makes it a little less error prone. 2019-02-19 01:44:00 +11:00
Niels Lyngsø e1d990e6e8 Only show wrapper when it has content to show 2019-02-18 14:13:23 +01:00
Niels Lyngsø ce8b29bfcc Fixes #4438, makes variant-picker push the width of input, moed border of input to input-wrapper. 2019-02-18 13:35:16 +01:00
Kenn Jacobsen 1fe1762724 Retain cculture in breadcrumbs 2019-02-14 15:02:54 +01:00
Kenn Jacobsen 65670ab539 Merge branch 'temp8' into v8-fix-dont-retain-cculture 2019-02-14 14:01:16 +01:00
Kenn Jacobsen 2ab3889b23 Don't retain cculture when navigating away from the current content 2019-02-07 18:15:21 +01:00
182 changed files with 2154 additions and 1489 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="3.4.4">
<metadata minClientVersion="4.1.0">
<id>UmbracoCms.Core</id>
<version>8.0.0</version>
<title>Umbraco Cms Core Binaries</title>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="3.4.4">
<metadata minClientVersion="4.1.0">
<id>UmbracoCms.Web</id>
<version>8.0.0</version>
<title>Umbraco Cms Core Binaries</title>
+2 -2
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="3.4.4">
<metadata minClientVersion="4.1.0">
<id>UmbracoCms</id>
<version>8.0.0</version>
<title>Umbraco Cms</title>
@@ -25,7 +25,7 @@
not want this to happen as the alpha of the next major is, really, the next major already.
-->
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.4.0, 2.999999)" />
<dependency id="Umbraco.ModelsBuilder.Ui" version="[8.0.0-alpha.38]" />
<dependency id="Umbraco.ModelsBuilder.Ui" version="[8.0.4]" />
<dependency id="ImageProcessor.Web" version="[4.10.0.100,4.999999)" />
<dependency id="ImageProcessor.Web.Config" version="[2.5.0.100,2.999999)" />
<dependency id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="[2.0.1,2.999999)" />
+46 -36
View File
@@ -126,45 +126,55 @@
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='Microsoft.CodeAnalysis.CSharp')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='log4net')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Data.SqlServerCe')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.ValueTuple')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Net.Http.Formatting')" xdt:Transform="Remove" />
</assemblyBinding>
</runtime>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-11.0.0.0" newVersion="11.0.0.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.2.1.0" newVersion="1.2.1.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.6.0" newVersion="5.2.6.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.6.0" newVersion="5.2.6.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.2.3.0" newVersion="1.2.3.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<system.web.webPages.razor xdt:Transform="Remove" />
-93
View File
@@ -1,93 +0,0 @@
param($installPath, $toolsPath, $package, $project)
Write-Host "installPath:" "${installPath}"
Write-Host "toolsPath:" "${toolsPath}"
Write-Host " "
if ($project) {
$dateTime = Get-Date -Format yyyyMMdd-HHmmss
# Create paths and list them
$projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName
Write-Host "projectPath:" "${projectPath}"
$backupPath = Join-Path $projectPath "App_Data\NuGetBackup\$dateTime"
Write-Host "backupPath:" "${backupPath}"
$copyLogsPath = Join-Path $backupPath "CopyLogs"
Write-Host "copyLogsPath:" "${copyLogsPath}"
$umbracoBinFolder = Join-Path $projectPath "bin"
Write-Host "umbracoBinFolder:" "${umbracoBinFolder}"
# Create backup folder and logs folder if it doesn't exist yet
New-Item -ItemType Directory -Force -Path $backupPath
New-Item -ItemType Directory -Force -Path $copyLogsPath
# After backing up, remove all umbraco dlls from bin folder in case dll files are included in the VS project
# See: http://issues.umbraco.org/issue/U4-4930
if(Test-Path $umbracoBinFolder) {
$umbracoBinBackupPath = Join-Path $backupPath "bin"
New-Item -ItemType Directory -Force -Path $umbracoBinBackupPath
robocopy $umbracoBinFolder $umbracoBinBackupPath /e /LOG:$copyLogsPath\UmbracoBinBackup.log
# Delete files Umbraco ships with
if(Test-Path $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll) { Remove-Item $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.Web.dll) { Remove-Item $umbracoBinFolder\Umbraco.Web.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.Core.dll) { Remove-Item $umbracoBinFolder\Umbraco.Core.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.ModelsBuilder.dll) { Remove-Item $umbracoBinFolder\Umbraco.ModelsBuilder.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.ModelsBuilder.AspNet.dll) { Remove-Item $umbracoBinFolder\Umbraco.ModelsBuilder.AspNet.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.Web.UI.dll) { Remove-Item $umbracoBinFolder\Umbraco.Web.UI.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.Examine.dll) { Remove-Item $umbracoBinFolder\Umbraco.Examine.dll -Force -Confirm:$false }
# Delete files Umbraco depends upon
$amd64Folder = Join-Path $umbracoBinFolder "amd64"
if(Test-Path $amd64Folder) { Remove-Item $amd64Folder -Force -Recurse -Confirm:$false }
$x86Folder = Join-Path $umbracoBinFolder "x86"
if(Test-Path $x86Folder) { Remove-Item $x86Folder -Force -Recurse -Confirm:$false }
if(Test-Path $umbracoBinFolder\AutoMapper.dll) { Remove-Item $umbracoBinFolder\AutoMapper.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\AutoMapper.Net4.dll) { Remove-Item $umbracoBinFolder\AutoMapper.Net4.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\ClientDependency.Core.dll) { Remove-Item $umbracoBinFolder\ClientDependency.Core.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\ClientDependency.Core.Mvc.dll) { Remove-Item $umbracoBinFolder\ClientDependency.Core.Mvc.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\CookComputing.XmlRpcV2.dll) { Remove-Item $umbracoBinFolder\CookComputing.XmlRpcV2.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Examine.dll) { Remove-Item $umbracoBinFolder\Examine.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\HtmlAgilityPack.dll) { Remove-Item $umbracoBinFolder\HtmlAgilityPack.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\ICSharpCode.SharpZipLib.dll) { Remove-Item $umbracoBinFolder\ICSharpCode.SharpZipLib.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\ImageProcessor.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\ImageProcessor.Web.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.Web.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Lucene.Net.dll) { Remove-Item $umbracoBinFolder\Lucene.Net.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll) { Remove-Item $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Owin.Host.SystemWeb.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Host.SystemWeb.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.Cookies.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.Cookies.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.OAuth.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.OAuth.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Web.Infrastructure.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Infrastructure.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Web.Helpers.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Helpers.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Web.Mvc.FixedDisplayModes.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Mvc.FixedDisplayModes.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\MiniProfiler.dll) { Remove-Item $umbracoBinFolder\MiniProfiler.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Newtonsoft.Json.dll) { Remove-Item $umbracoBinFolder\Newtonsoft.Json.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Owin.dll) { Remove-Item $umbracoBinFolder\Owin.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Semver.dll) { Remove-Item $umbracoBinFolder\Semver.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Collections.Immutable.dll) { Remove-Item $umbracoBinFolder\System.Collections.Immutable.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Reflection.Metadata.dll) { Remove-Item $umbracoBinFolder\System.Reflection.Metadata.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Net.Http.Extensions.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Extensions.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Net.Http.Formatting.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Formatting.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Net.Http.Primitives.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Primitives.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.Helpers.dll) { Remove-Item $umbracoBinFolder\System.Web.Helpers.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.Http.dll) { Remove-Item $umbracoBinFolder\System.Web.Http.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.Http.WebHost.dll) { Remove-Item $umbracoBinFolder\System.Web.Http.WebHost.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.Mvc.dll) { Remove-Item $umbracoBinFolder\System.Web.Mvc.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.Razor.dll) { Remove-Item $umbracoBinFolder\System.Web.Razor.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.WebPages.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.WebPages.Deployment.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.Deployment.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.WebPages.Razor.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.Razor.dll -Force -Confirm:$false }
}
}
-27
View File
@@ -11,37 +11,15 @@ if ($project) {
# Create paths and list them
$projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName
Write-Host "projectPath:" "${projectPath}"
$backupPath = Join-Path $projectPath "App_Data\NuGetBackup\$dateTime"
Write-Host "backupPath:" "${backupPath}"
$copyLogsPath = Join-Path $backupPath "CopyLogs"
Write-Host "copyLogsPath:" "${copyLogsPath}"
$webConfigSource = Join-Path $projectPath "Web.config"
Write-Host "webConfigSource:" "${webConfigSource}"
$configFolder = Join-Path $projectPath "Config"
Write-Host "configFolder:" "${configFolder}"
# Create backup folder and logs folder if it doesn't exist yet
New-Item -ItemType Directory -Force -Path $backupPath
New-Item -ItemType Directory -Force -Path $copyLogsPath
# Create a backup of original web.config
Copy-Item $webConfigSource $backupPath -Force
# Backup config files folder
if(Test-Path $configFolder) {
$umbracoBackupPath = Join-Path $backupPath "Config"
New-Item -ItemType Directory -Force -Path $umbracoBackupPath
robocopy $configFolder $umbracoBackupPath /e /LOG:$copyLogsPath\ConfigBackup.log
}
# Copy umbraco and umbraco_files from package to project folder
$umbracoFolder = Join-Path $projectPath "Umbraco"
New-Item -ItemType Directory -Force -Path $umbracoFolder
$umbracoFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco"
$umbracoBackupPath = Join-Path $backupPath "Umbraco"
New-Item -ItemType Directory -Force -Path $umbracoBackupPath
robocopy $umbracoFolder $umbracoBackupPath /e /LOG:$copyLogsPath\UmbracoBackup.log
robocopy $umbracoFolderSource $umbracoFolder /is /it /e /xf UI.xml /LOG:$copyLogsPath\UmbracoCopy.log
$copyWebconfig = $true
@@ -100,11 +78,6 @@ if ($project) {
}
}
$installFolder = Join-Path $projectPath "Install"
if(Test-Path $installFolder) {
Remove-Item $installFolder -Force -Recurse -Confirm:$false
}
# Open appropriate readme
if($copyWebconfig -eq $true)
{
+2 -2
View File
@@ -18,5 +18,5 @@ using System.Resources;
[assembly: AssemblyVersion("8.0.0")]
// these are FYI and changed automatically
[assembly: AssemblyFileVersion("8.0.0")]
[assembly: AssemblyInformationalVersion("8.0.0-alpha.58")]
[assembly: AssemblyFileVersion("8.0.1")]
[assembly: AssemblyInformationalVersion("8.0.1")]
@@ -30,7 +30,7 @@ namespace Umbraco.Core.Composing
protected override IComponent CreateItem(IFactory factory, Type itemType)
{
using (_logger.DebugDuration<Composers>($"Creating {itemType.FullName}.", $"Created {itemType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
using (_logger.DebugDuration<ComponentCollectionBuilder>($"Creating {itemType.FullName}.", $"Created {itemType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
{
return base.CreateItem(factory, itemType);
}
@@ -79,7 +79,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
composition.RegisterUnique<IFileSystems>(factory => factory.GetInstance<IO.FileSystems>());
// register the scheme for media paths
composition.RegisterUnique<IMediaPathScheme, TwoGuidsMediaPathScheme>();
composition.RegisterUnique<IMediaPathScheme, UniqueMediaPathScheme>();
// register the IMediaFileSystem implementation
composition.RegisterFileSystem<IMediaFileSystem, MediaFileSystem>();
+1 -2
View File
@@ -8,7 +8,6 @@
/// <summary>
/// Compose.
/// </summary>
/// <param name="composition"></param>
void Compose(Composition composition);
}
}
}
+2 -4
View File
@@ -4,10 +4,8 @@
/// Represents a core <see cref="IComposer"/>.
/// </summary>
/// <remarks>
/// <para>All core composers are required by (compose before) all user composers,
/// and require (compose after) all runtime composers.</para>
/// <para>Core composers compose after the initial composer, and before user composers.</para>
/// </remarks>
[ComposeAfter(typeof(IRuntimeComposer))]
public interface ICoreComposer : IComposer
{ }
}
}
@@ -1,11 +0,0 @@
namespace Umbraco.Core.Composing
{
/// <summary>
/// Represents a runtime <see cref="IComposer"/>.
/// </summary>
/// <remarks>
/// <para>All runtime composers are required by (compose before) all core composers</para>
/// </remarks>
public interface IRuntimeComposer : IComposer
{ }
}
+2 -2
View File
@@ -4,9 +4,9 @@
/// Represents a user <see cref="IComposer"/>.
/// </summary>
/// <remarks>
/// <para>All user composers require (compose after) all core composers.</para>
/// <para>User composers compose after core composers, and before the final composer.</para>
/// </remarks>
[ComposeAfter(typeof(ICoreComposer))]
public interface IUserComposer : IComposer
{ }
}
}
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Composing
{
/// <summary>
/// Provides a base class for collections of types.
/// </summary>
public abstract class TypeCollectionBuilderBase<TCollection, TConstraint> : ICollectionBuilder<TCollection, Type>
where TCollection : class, IBuilderCollection<Type>
{
private readonly HashSet<Type> _types = new HashSet<Type>();
private Type Validate(Type type, string action)
{
if (!typeof(TConstraint).IsAssignableFrom(type))
throw new InvalidOperationException($"Cannot {action} type {type.FullName} as it does not inherit from/implement {typeof(TConstraint).FullName}.");
return type;
}
public void Add(Type type) => _types.Add(Validate(type, "add"));
public void Add<T>() => Add(typeof(T));
public void Add(IEnumerable<Type> types)
{
foreach (var type in types) Add(type);
}
public void Remove(Type type) => _types.Remove(Validate(type, "remove"));
public void Remove<T>() => Remove(typeof(T));
public TCollection CreateCollection(IFactory factory)
{
return factory.CreateInstance<TCollection>(_types);
}
public void RegisterWith(IRegister register)
{
register.Register(CreateCollection, Lifetime.Singleton);
}
}
}
@@ -18,46 +18,6 @@ namespace Umbraco.Core
/// </summary>
public static partial class CompositionExtensions
{
#region FileSystems
/// <summary>
/// Registers a filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <typeparam name="TImplementing">The implementing type.</typeparam>
/// <param name="composition">The composition.</param>
/// <returns>The register.</returns>
public static void RegisterFileSystem<TFileSystem, TImplementing>(this Composition composition)
where TImplementing : FileSystemWrapper, TFileSystem
where TFileSystem : class
{
composition.RegisterUnique<TFileSystem>(factory =>
{
var fileSystems = factory.GetInstance<FileSystems>();
var supporting = factory.GetInstance<SupportingFileSystems>();
return fileSystems.GetFileSystem<TImplementing>(supporting.For<TFileSystem>());
});
}
/// <summary>
/// Registers a filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <param name="composition">The composition.</param>
/// <returns>The register.</returns>
public static void RegisterFileSystem<TFileSystem>(this Composition composition)
where TFileSystem : FileSystemWrapper
{
composition.RegisterUnique(factory =>
{
var fileSystems = factory.GetInstance<FileSystems>();
var supporting = factory.GetInstance<SupportingFileSystems>();
return fileSystems.GetFileSystem<TFileSystem>(supporting.For<TFileSystem>());
});
}
#endregion
#region Collection Builders
/// <summary>
@@ -10,8 +10,6 @@ namespace Umbraco.Core
/// </summary>
public static partial class CompositionExtensions
{
#region Essentials
/// <summary>
/// Registers essential services.
/// </summary>
@@ -33,36 +31,5 @@ namespace Umbraco.Core
composition.RegisterUnique(typeLoader);
composition.RegisterUnique(state);
}
#endregion
#region Unique
/// <summary>
/// Registers a unique service as its own implementation.
/// </summary>
public static void RegisterUnique<TService>(this Composition composition)
=> composition.RegisterUnique(typeof(TService), typeof(TService));
/// <summary>
/// Registers a unique service with an implementation type.
/// </summary>
public static void RegisterUnique<TService, TImplementing>(this Composition composition)
=> composition.RegisterUnique(typeof(TService), typeof(TImplementing));
/// <summary>
/// Registers a unique service with an implementation type, for a target.
/// </summary>
public static void RegisterUniqueFor<TService, TTarget, TImplementing>(this Composition composition)
where TService : class
=> composition.RegisterUniqueFor<TService, TTarget>(typeof(TImplementing));
/// <summary>
/// Registers a unique service with an implementing instance.
/// </summary>
public static void RegisterUnique<TService>(this Composition composition, TService instance)
=> composition.RegisterUnique(typeof(TService), instance);
#endregion
}
}
@@ -0,0 +1,47 @@
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods to the <see cref="Composition"/> class.
/// </summary>
public static partial class CompositionExtensions
{
/// <summary>
/// Registers a filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <typeparam name="TImplementing">The implementing type.</typeparam>
/// <param name="composition">The composition.</param>
/// <returns>The register.</returns>
public static void RegisterFileSystem<TFileSystem, TImplementing>(this Composition composition)
where TImplementing : FileSystemWrapper, TFileSystem
where TFileSystem : class
{
composition.RegisterUnique<TFileSystem>(factory =>
{
var fileSystems = factory.GetInstance<FileSystems>();
var supporting = factory.GetInstance<SupportingFileSystems>();
return fileSystems.GetFileSystem<TImplementing>(supporting.For<TFileSystem>());
});
}
/// <summary>
/// Registers a filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <param name="composition">The composition.</param>
/// <returns>The register.</returns>
public static void RegisterFileSystem<TFileSystem>(this Composition composition)
where TFileSystem : FileSystemWrapper
{
composition.RegisterUnique(factory =>
{
var fileSystems = factory.GetInstance<FileSystems>();
var supporting = factory.GetInstance<SupportingFileSystems>();
return fileSystems.GetFileSystem<TFileSystem>(supporting.For<TFileSystem>());
});
}
}
}
@@ -0,0 +1,35 @@
using Umbraco.Core.Composing;
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods to the <see cref="Composition"/> class.
/// </summary>
public static partial class CompositionExtensions
{
/// <summary>
/// Registers a unique service as its own implementation.
/// </summary>
public static void RegisterUnique<TService>(this Composition composition)
=> composition.RegisterUnique(typeof(TService), typeof(TService));
/// <summary>
/// Registers a unique service with an implementation type.
/// </summary>
public static void RegisterUnique<TService, TImplementing>(this Composition composition)
=> composition.RegisterUnique(typeof(TService), typeof(TImplementing));
/// <summary>
/// Registers a unique service with an implementation type, for a target.
/// </summary>
public static void RegisterUniqueFor<TService, TTarget, TImplementing>(this Composition composition)
where TService : class
=> composition.RegisterUniqueFor<TService, TTarget>(typeof(TImplementing));
/// <summary>
/// Registers a unique service with an implementing instance.
/// </summary>
public static void RegisterUnique<TService>(this Composition composition, TService instance)
=> composition.RegisterUnique(typeof(TService), instance);
}
}
@@ -128,6 +128,7 @@ namespace Umbraco.Core.Configuration
if (routes == null) throw new ArgumentNullException(nameof(routes));
//check if the current request matches a route, if so then it is reserved.
//TODO: This value should be cached! Else this is doing double routing in MVC every request!
var route = routes.GetRouteData(httpContext);
if (route != null)
return true;
+67
View File
@@ -40,5 +40,72 @@ namespace Umbraco.Core
public DecomposedGuid(Guid value) : this() => this.Value = value;
}
private static readonly char[] Base32Table =
{
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5'
};
/// <summary>
/// Converts a Guid into a base-32 string.
/// </summary>
/// <param name="guid">A Guid.</param>
/// <param name="length">The string length.</param>
/// <returns>A base-32 encoded string.</returns>
/// <remarks>
/// <para>A base-32 string representation of a Guid is the shortest, efficient, representation
/// that is case insensitive (base-64 is case sensitive).</para>
/// <para>Length must be 1-26, anything else becomes 26.</para>
/// </remarks>
public static string ToBase32String(Guid guid, int length = 26)
{
if (length <= 0 || length > 26)
length = 26;
var bytes = guid.ToByteArray(); // a Guid is 128 bits ie 16 bytes
// this could be optimized by making it unsafe,
// and fixing the table + bytes + chars (see Convert.ToBase64CharArray)
// each block of 5 bytes = 5*8 = 40 bits
// becomes 40 bits = 8*5 = 8 byte-32 chars
// a Guid is 3 blocks + 8 bits
// so it turns into a 3*8+2 = 26 chars string
var chars = new char[length];
var i = 0;
var j = 0;
while (i < 15)
{
if (j == length) break;
chars[j++] = Base32Table[(bytes[i] & 0b1111_1000) >> 3];
if (j == length) break;
chars[j++] = Base32Table[((bytes[i] & 0b0000_0111) << 2) | ((bytes[i + 1] & 0b1100_0000) >> 6)];
if (j == length) break;
chars[j++] = Base32Table[(bytes[i + 1] & 0b0011_1110) >> 1];
if (j == length) break;
chars[j++] = Base32Table[(bytes[i + 1] & 0b0000_0001) | ((bytes[i + 2] & 0b1111_0000) >> 4)];
if (j == length) break;
chars[j++] = Base32Table[((bytes[i + 2] & 0b0000_1111) << 1) | ((bytes[i + 3] & 0b1000_0000) >> 7)];
if (j == length) break;
chars[j++] = Base32Table[(bytes[i + 3] & 0b0111_1100) >> 2];
if (j == length) break;
chars[j++] = Base32Table[((bytes[i + 3] & 0b0000_0011) << 3) | ((bytes[i + 4] & 0b1110_0000) >> 5)];
if (j == length) break;
chars[j++] = Base32Table[bytes[i + 4] & 0b0001_1111];
i += 5;
}
if (j < length)
chars[j++] = Base32Table[(bytes[i] & 0b1111_1000) >> 3];
if (j < length)
chars[j] = Base32Table[(bytes[i] & 0b0000_0111) << 2];
return new string(chars);
}
}
}
+48 -18
View File
@@ -29,7 +29,7 @@ namespace Umbraco.Core.IO
// shadow support
private readonly List<ShadowWrapper> _shadowWrappers = new List<ShadowWrapper>();
private readonly object _shadowLocker = new object();
private static Guid _shadowCurrentId = Guid.Empty; // static - unique!!
private static string _shadowCurrentId; // static - unique!!
#region Constructor
// DI wants a public ctor
@@ -45,13 +45,13 @@ namespace Umbraco.Core.IO
_shadowWrappers.Clear();
_filesystems.Clear();
Volatile.Write(ref _wkfsInitialized, false);
_shadowCurrentId = Guid.Empty;
_shadowCurrentId = null;
}
// for tests only, totally unsafe
internal static void ResetShadowId()
{
_shadowCurrentId = Guid.Empty;
_shadowCurrentId = null;
}
// set by the scope provider when taking control of filesystems
@@ -126,11 +126,11 @@ namespace Umbraco.Core.IO
var scriptsFileSystem = new PhysicalFileSystem(SystemDirectories.Scripts);
var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
_macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", IsScoped);
_partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", IsScoped);
_macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "macro-partials", IsScoped);
_partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "partials", IsScoped);
_stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, "css", IsScoped);
_scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", IsScoped);
_mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", IsScoped);
_mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "views", IsScoped);
// TODO: do we need a lock here?
_shadowWrappers.Add(_macroPartialFileSystem);
@@ -146,6 +146,11 @@ namespace Umbraco.Core.IO
#region Providers
private readonly Dictionary<Type, string> _paths = new Dictionary<Type, string>();
// internal for tests
internal IReadOnlyDictionary<Type, string> Paths => _paths;
/// <summary>
/// Gets a strongly-typed filesystem.
/// </summary>
@@ -162,10 +167,33 @@ namespace Umbraco.Core.IO
return (TFileSystem) _filesystems.GetOrAdd(typeof(TFileSystem), _ => new Lazy<IFileSystem>(() =>
{
var name = typeof(TFileSystem).FullName;
if (name == null) throw new Exception("panic!");
var typeofTFileSystem = typeof(TFileSystem);
var shadowWrapper = CreateShadowWrapper(supporting, "typed/" + name);
// path must be unique and not collide with paths used in CreateWellKnownFileSystems
// for our well-known 'media' filesystem we can use the short 'media' path
// for others, put them under 'x/' and use ... something
string path;
if (typeofTFileSystem == typeof(MediaFileSystem))
{
path = "media";
}
else
{
lock (_paths)
{
if (!_paths.TryGetValue(typeofTFileSystem, out path))
{
path = Guid.NewGuid().ToString("N").Substring(0, 6);
while (_paths.ContainsValue(path)) // this can't loop forever, right?
path = Guid.NewGuid().ToString("N").Substring(0, 6);
_paths[typeofTFileSystem] = path;
}
}
path = "x/" + path;
}
var shadowWrapper = CreateShadowWrapper(supporting, path);
return _container.CreateInstance<TFileSystem>(shadowWrapper);
})).Value;
}
@@ -179,35 +207,37 @@ namespace Umbraco.Core.IO
// global shadow for the entire application, so great care should be taken to ensure that the
// application is *not* doing anything else when using a shadow.
internal ICompletable Shadow(Guid id)
internal ICompletable Shadow()
{
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
var id = ShadowWrapper.CreateShadowId();
return new ShadowFileSystems(this, id); // will invoke BeginShadow and EndShadow
}
internal void BeginShadow(Guid id)
internal void BeginShadow(string id)
{
lock (_shadowLocker)
{
// if we throw here, it means that something very wrong happened.
if (_shadowCurrentId != Guid.Empty)
if (_shadowCurrentId != null)
throw new InvalidOperationException("Already shadowing.");
_shadowCurrentId = id;
_logger.Debug<ShadowFileSystems>("Shadow '{ShadowId}'", id);
_logger.Debug<ShadowFileSystems>("Shadow '{ShadowId}'", _shadowCurrentId);
foreach (var wrapper in _shadowWrappers)
wrapper.Shadow(id);
wrapper.Shadow(_shadowCurrentId);
}
}
internal void EndShadow(Guid id, bool completed)
internal void EndShadow(string id, bool completed)
{
lock (_shadowLocker)
{
// if we throw here, it means that something very wrong happened.
if (_shadowCurrentId == Guid.Empty)
if (_shadowCurrentId == null)
throw new InvalidOperationException("Not shadowing.");
if (id != _shadowCurrentId)
throw new InvalidOperationException("Not the current shadow.");
@@ -228,7 +258,7 @@ namespace Umbraco.Core.IO
}
}
_shadowCurrentId = Guid.Empty;
_shadowCurrentId = null;
if (exceptions.Count > 0)
throw new AggregateException(completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions);
@@ -240,7 +270,7 @@ namespace Umbraco.Core.IO
lock (_shadowLocker)
{
var wrapper = new ShadowWrapper(filesystem, shadowPath, IsScoped);
if (_shadowCurrentId != Guid.Empty)
if (_shadowCurrentId != null)
wrapper.Shadow(_shadowCurrentId);
_shadowWrappers.Add(wrapper);
return wrapper;
@@ -7,16 +7,21 @@ namespace Umbraco.Core.IO.MediaPathSchemes
/// Implements a combined-guids media path scheme.
/// </summary>
/// <remarks>
/// <para>Path is "{combinedGuid}/{filename>}" where combinedGuid is a combination of itemGuid and propertyGuid.</para>
/// <para>Path is "{combinedGuid}/{filename}" where combinedGuid is a combination of itemGuid and propertyGuid.</para>
/// <para>This scheme is dangerous, as it does not prevent potential collisions.</para>
/// </remarks>
public class CombinedGuidsMediaPathScheme : IMediaPathScheme
{
private const int DirectoryLength = 8;
/// <inheritdoc />
public string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null)
{
// assumes that cuid and puid keys can be trusted - and that a single property type
// for a single content cannot store two different files with the same name
var directory = HexEncoder.Encode(GuidUtils.Combine(itemGuid, propertyGuid).ToByteArray()/*'/', 2, 4*/); // could use ext to fragment path eg 12/e4/f2/...
var combinedGuid = GuidUtils.Combine(itemGuid, propertyGuid);
var directory = GuidUtils.ToBase32String(combinedGuid, DirectoryLength); // see also HexEncoder, we may want to fragment path eg 12/e4/f3...
return Path.Combine(directory, filename).Replace('\\', '/');
}
@@ -0,0 +1,38 @@
using System;
using System.IO;
namespace Umbraco.Core.IO.MediaPathSchemes
{
/// <summary>
/// Implements a unique directory media path scheme.
/// </summary>
/// <remarks>
/// <para>This scheme provides short paths, yet handle potential collisions.</para>
/// </remarks>
public class UniqueMediaPathScheme : IMediaPathScheme
{
private const int DirectoryLength = 8;
/// <inheritdoc />
public string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null)
{
string directory;
// no point "combining" guids if all we want is some random guid - just get a new one
// and then, because we don't want collisions, ensure that the directory does not already exist
// (should be quite rare, but eh...)
do
{
var combinedGuid = Guid.NewGuid();
directory = GuidUtils.ToBase32String(combinedGuid, DirectoryLength); // see also HexEncoder, we may want to fragment path eg 12/e4/f3...
} while (fileSystem.DirectoryExists(directory));
return Path.Combine(directory, filename).Replace('\\', '/');
}
/// <inheritdoc />
public string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath) => Path.GetDirectoryName(filepath);
}
}
@@ -304,7 +304,13 @@ namespace Umbraco.Core.IO
// permissions to reach that path, but it may nevertheless be outside of
// our root path, due to relative segments, so better check
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar))
{
// this says that 4.7.2 supports long paths - but Windows does not
// https://docs.microsoft.com/en-us/dotnet/api/system.io.pathtoolongexception?view=netframework-4.7.2
if (path.Length > 260)
throw new PathTooLongException($"Path {path} is too long.");
return path;
}
// nothing prevents us to reach the file, security-wise, yet it is outside
// this filesystem's root - throw
+2 -2
View File
@@ -10,7 +10,7 @@ namespace Umbraco.Core.IO
private bool _completed;
// invoked by the filesystems when shadowing
public ShadowFileSystems(FileSystems fileSystems, Guid id)
public ShadowFileSystems(FileSystems fileSystems, string id)
{
_fileSystems = fileSystems;
Id = id;
@@ -19,7 +19,7 @@ namespace Umbraco.Core.IO
}
// for tests
public Guid Id { get; }
public string Id { get; }
// invoked by the scope when exiting, if completed
public void Complete()
+27 -1
View File
@@ -22,7 +22,33 @@ namespace Umbraco.Core.IO
_isScoped = isScoped;
}
internal void Shadow(Guid id)
public static string CreateShadowId()
{
const int retries = 50; // avoid infinite loop
const int idLength = 8; // 6 chars
// shorten a Guid to idLength chars, and see whether it collides
// with an existing directory or not - if it does, try again, and
// we should end up with a unique identifier eventually - but just
// detect infinite loops (just in case)
for (var i = 0; i < retries; i++)
{
var id = GuidUtils.ToBase32String(Guid.NewGuid(), idLength);
var virt = ShadowFsPath + "/" + id;
var shadowDir = IOHelper.MapPath(virt);
if (Directory.Exists(shadowDir))
continue;
Directory.CreateDirectory(shadowDir);
return id;
}
throw new Exception($"Could not get a shadow identifier (tried {retries} times)");
}
internal void Shadow(string id)
{
// note: no thread-safety here, because ShadowFs is thread-safe due to the check
// on ShadowFileSystemsScope.None - and if None is false then we should be running
@@ -281,7 +281,7 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -37, EditorAlias = Constants.PropertyEditors.Aliases.ColorPicker, DbType = "Nvarchar" });
InsertDataTypeDto(Constants.DataTypes.DropDownSingle, Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":false}");
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -40, EditorAlias = Constants.PropertyEditors.Aliases.RadioButtonList, DbType = "Nvarchar" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -41, EditorAlias = "Umbraco.Date", DbType = "Date" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -41, EditorAlias = "Umbraco.DateTime", DbType = "Date", Configuration = "{\"format\":\"YYYY-MM-DD\"}" });
InsertDataTypeDto(Constants.DataTypes.DropDownMultiple, Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":true}");
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -43, EditorAlias = Constants.PropertyEditors.Aliases.CheckBoxList, DbType = "Nvarchar" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1041, EditorAlias = Constants.PropertyEditors.Aliases.Tags, DbType = "Ntext",
@@ -5,6 +5,7 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.Migrations.Upgrade.V_7_12_0;
using Umbraco.Core.Migrations.Upgrade.V_7_14_0;
using Umbraco.Core.Migrations.Upgrade.V_8_0_0;
using Umbraco.Core.Migrations.Upgrade.V_8_0_1;
namespace Umbraco.Core.Migrations.Upgrade
{
@@ -137,6 +138,8 @@ namespace Umbraco.Core.Migrations.Upgrade
To<RenameLabelAndRichTextPropertyEditorAliases>("{E0CBE54D-A84F-4A8F-9B13-900945FD7ED9}");
To<MergeDateAndDateTimePropertyEditor>("{78BAF571-90D0-4D28-8175-EF96316DA789}");
To<ChangeNuCacheJsonFormat>("{80C0A0CB-0DD5-4573-B000-C4B7C313C70D}");
//FINAL
@@ -167,7 +170,7 @@ namespace Umbraco.Core.Migrations.Upgrade
From("{init-7.12.4}").To("{init-7.10.0}"); // same as 7.12.0
From("{init-7.13.0}").To("{init-7.10.0}"); // same as 7.12.0
From("{init-7.13.1}").To("{init-7.10.0}"); // same as 7.12.0
// 7.14.0 has migrations, handle it...
// clone going from 7.10 to 1350617A (the last one before we started to merge 7.12 migrations), then
// clone going from CF51B39B (after 7.12 migrations) to 0009109C (the last one before we started to merge 7.12 migrations),
@@ -0,0 +1,16 @@
using Umbraco.Core.Migrations.PostMigrations;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_1
{
public class ChangeNuCacheJsonFormat : MigrationBase
{
public ChangeNuCacheJsonFormat(IMigrationContext context) : base(context)
{ }
public override void Migrate()
{
// nothing - just adding the post-migration
Context.AddPostMigration<RebuildPublishedSnapshot>();
}
}
}
@@ -1,7 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Collections;
using Umbraco.Core.Composing;
using Umbraco.Core.Exceptions;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
namespace Umbraco.Core.Models
{
@@ -101,35 +105,6 @@ namespace Umbraco.Core.Models
}
}
/// <summary>
/// Validates the content item's properties pass variant rules
/// </summary>
/// <para>If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor
/// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty.</para>
public static Property[] ValidateProperties(this IContentBase content, string culture = "*")
{
// select invalid properties
return content.Properties.Where(x =>
{
// if culture is null, we validate invariant properties only
// if culture is '*' we validate both variant and invariant properties, automatically
// if culture is specific eg 'en-US' we both too, but explicitly
var varies = x.PropertyType.VariesByCulture();
if (culture == null)
return !(varies || x.IsValid(null)); // validate invariant property, invariant culture
if (culture == "*")
return !x.IsValid(culture); // validate property, all cultures
return varies
? !x.IsValid(culture) // validate variant property, explicit culture
: !x.IsValid(null); // validate invariant property, explicit culture
})
.ToArray();
}
public static void SetPublishInfo(this IContent content, string culture, string name, DateTime date)
{
if (string.IsNullOrWhiteSpace(name))
@@ -191,6 +166,13 @@ namespace Umbraco.Core.Models
content.CultureInfos.AddOrUpdate(culture, name, date);
}
/// <summary>
/// Sets the publishing values for names and properties.
/// </summary>
/// <param name="content"></param>
/// <param name="culture"></param>
/// <returns>A value indicating whether it was possible to publish the names and values for the specified
/// culture(s). The method may fail if required names are not set, but it does NOT validate property data</returns>
public static bool PublishCulture(this IContent content, string culture = "*")
{
culture = culture.NullOrWhiteSpaceAsNull();
@@ -201,10 +183,6 @@ namespace Umbraco.Core.Models
if (!content.ContentType.SupportsPropertyVariation(culture, "*", true))
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\".");
// the values we want to publish should be valid
if (content.ValidateProperties(culture).Any())
return false;
var alsoInvariant = false;
if (culture == "*") // all cultures
{
+2 -2
View File
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Models
[DataContract(IsReference = true)]
public class ContentType : ContentTypeCompositionBase, IContentType
{
public const bool IsPublishingConst = true;
public const bool SupportsPublishingConst = true;
private int _defaultTemplate;
private IEnumerable<ITemplate> _allowedTemplates;
@@ -45,7 +45,7 @@ namespace Umbraco.Core.Models
public override ISimpleContentType ToSimple() => new SimpleContentType(this);
/// <inheritdoc />
public override bool IsPublishing => IsPublishingConst;
public override bool SupportsPublishing => SupportsPublishingConst;
//Custom comparer for enumerable
private static readonly DelegateEqualityComparer<IEnumerable<ITemplate>> TemplateComparer = new DelegateEqualityComparer<IEnumerable<ITemplate>>(
+5 -5
View File
@@ -40,7 +40,7 @@ namespace Umbraco.Core.Models
// actually OK as IsPublishing is constant
// ReSharper disable once VirtualMemberCallInConstructor
_noGroupPropertyTypes = new PropertyTypeCollection(IsPublishing);
_noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing);
_noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged;
_variations = ContentVariation.Nothing;
@@ -61,7 +61,7 @@ namespace Umbraco.Core.Models
// actually OK as IsPublishing is constant
// ReSharper disable once VirtualMemberCallInConstructor
_noGroupPropertyTypes = new PropertyTypeCollection(IsPublishing);
_noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing);
_noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged;
_variations = ContentVariation.Nothing;
@@ -70,7 +70,7 @@ namespace Umbraco.Core.Models
public abstract ISimpleContentType ToSimple();
/// <summary>
/// Gets a value indicating whether the content type is publishing.
/// Gets a value indicating whether the content type supports publishing.
/// </summary>
/// <remarks>
/// <para>A publishing content type supports draft and published values for properties.
@@ -80,7 +80,7 @@ namespace Umbraco.Core.Models
/// the draft or published value of a property returns the same thing, and publishing
/// a value property has no effect.</para>
/// </remarks>
public abstract bool IsPublishing { get; }
public abstract bool SupportsPublishing { get; }
//Custom comparer for enumerable
private static readonly DelegateEqualityComparer<IEnumerable<ContentTypeSort>> ContentTypeSortComparer =
@@ -257,7 +257,7 @@ namespace Umbraco.Core.Models
{
if (_noGroupPropertyTypes != null)
_noGroupPropertyTypes.CollectionChanged -= PropertyTypesChanged;
_noGroupPropertyTypes = new PropertyTypeCollection(IsPublishing, value);
_noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing, value);
_noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged;
PropertyTypesChanged(_noGroupPropertyTypes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
@@ -226,7 +226,7 @@ namespace Umbraco.Core.Models
return null;
// create the new group
var group = new PropertyGroup(IsPublishing) { Name = name, SortOrder = 0 };
var group = new PropertyGroup(SupportsPublishing) { Name = name, SortOrder = 0 };
// check if it is inherited - there might be more than 1 but we want the 1st, to
// reuse its sort order - if there are more than 1 and they have different sort
+2 -2
View File
@@ -10,7 +10,7 @@ namespace Umbraco.Core.Models
[DataContract(IsReference = true)]
public class MediaType : ContentTypeCompositionBase, IMediaType
{
public const bool IsPublishingConst = false;
public const bool SupportsPublishingConst = false;
/// <summary>
/// Constuctor for creating a MediaType with the parent's id.
@@ -45,7 +45,7 @@ namespace Umbraco.Core.Models
public override ISimpleContentType ToSimple() => new SimpleContentType(this);
/// <inheritdoc />
public override bool IsPublishing => IsPublishingConst;
public override bool SupportsPublishing => SupportsPublishingConst;
/// <inheritdoc />
IMediaType IMediaType.DeepCloneWithResetIdentities(string newAlias) => (IMediaType)DeepCloneWithResetIdentities(newAlias);
+2 -2
View File
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Models
[DataContract(IsReference = true)]
public class MemberType : ContentTypeCompositionBase, IMemberType
{
public const bool IsPublishingConst = false;
public const bool SupportsPublishingConst = false;
//Dictionary is divided into string: PropertyTypeAlias, Tuple: MemberCanEdit, VisibleOnProfile, PropertyTypeId
private string _alias;
@@ -35,7 +35,7 @@ namespace Umbraco.Core.Models
public override ISimpleContentType ToSimple() => new SimpleContentType(this);
/// <inheritdoc />
public override bool IsPublishing => IsPublishingConst;
public override bool SupportsPublishing => SupportsPublishingConst;
public override ContentVariation Variations
{
+4 -54
View File
@@ -184,7 +184,7 @@ namespace Umbraco.Core.Models
{
if (pvalue == null) return null;
return PropertyType.IsPublishing
return PropertyType.SupportsPublishing
? (published ? pvalue.PublishedValue : pvalue.EditedValue)
: pvalue.EditedValue;
}
@@ -244,7 +244,7 @@ namespace Umbraco.Core.Models
{
if (pvalue == null) return;
if (!PropertyType.IsPublishing)
if (!PropertyType.SupportsPublishing)
throw new NotSupportedException("Property type does not support publishing.");
var origValue = pvalue.PublishedValue;
pvalue.PublishedValue = PropertyType.ConvertAssignedValue(pvalue.EditedValue);
@@ -255,7 +255,7 @@ namespace Umbraco.Core.Models
{
if (pvalue == null) return;
if (!PropertyType.IsPublishing)
if (!PropertyType.SupportsPublishing)
throw new NotSupportedException("Property type does not support publishing.");
var origValue = pvalue.PublishedValue;
pvalue.PublishedValue = PropertyType.ConvertAssignedValue(null);
@@ -288,7 +288,7 @@ namespace Umbraco.Core.Models
{
var (pvalue, _) = GetPValue(culture, segment, true);
if (published && PropertyType.IsPublishing)
if (published && PropertyType.SupportsPublishing)
pvalue.PublishedValue = value;
else
pvalue.EditedValue = value;
@@ -332,56 +332,6 @@ namespace Umbraco.Core.Models
return (pvalue, change);
}
/// <summary>
/// Gets a value indicating whether the property has valid values.
/// </summary>
internal bool IsValid(string culture = "*", string segment = "*")
{
// TODO: validating values shouldn't be done here, this calls in to IsValidValue
culture = culture.NullOrWhiteSpaceAsNull();
segment = segment.NullOrWhiteSpaceAsNull();
// if validating invariant/neutral, and it is supported, validate
// (including ensuring that the value exists, if mandatory)
if ((culture == null || culture == "*") && (segment == null || segment == "*") && PropertyType.SupportsVariation(null, null))
if (!IsValidValue(_pvalue?.EditedValue))
return false;
// if validating only invariant/neutral, we are good
if (culture == null && segment == null)
return true;
// if nothing else to validate, we are good
if ((culture == null || culture == "*") && (segment == null || segment == "*") && !PropertyType.VariesByCulture())
return true;
// for anything else, validate the existing values (including mandatory),
// but we cannot validate mandatory globally (we don't know the possible cultures and segments)
if (_vvalues == null) return culture == "*" || IsValidValue(null);
var pvalues = _vvalues.Where(x =>
PropertyType.SupportsVariation(x.Value.Culture, x.Value.Segment, true) && // the value variation is ok
(culture == "*" || x.Value.Culture.InvariantEquals(culture)) && // the culture matches
(segment == "*" || x.Value.Segment.InvariantEquals(segment))) // the segment matches
.Select(x => x.Value)
.ToList();
return pvalues.Count == 0 || pvalues.All(x => IsValidValue(x.EditedValue));
}
/// <summary>
/// Boolean indicating whether the passed in value is valid
/// </summary>
/// <param name="value"></param>
/// <returns>True is property value is valid, otherwise false</returns>
private bool IsValidValue(object value)
{
// TODO: this shouldn't exist here, the model itself shouldn't be responsible for it's own validation and this requires singleton access
return PropertyType.IsPropertyValueValid(value);
}
protected override void PerformDeepClone(object clone)
{
base.PerformDeepClone(clone);
+15 -14
View File
@@ -82,9 +82,22 @@ namespace Umbraco.Core.Models
}
/// <summary>
/// Gets a value indicating whether the content type, owning this property type, is publishing.
/// Gets a value indicating whether the content type owning this property type is publishing.
/// </summary>
public bool IsPublishing { get; internal set; }
/// <remarks>
/// <para>A publishing content type supports draft and published values for properties.
/// It is possible to retrieve either the draft (default) or published value of a property.
/// Setting the value always sets the draft value, which then needs to be published.</para>
/// <para>A non-publishing content type only supports one value for properties. Getting
/// the draft or published value of a property returns the same thing, and publishing
/// a value property has no effect.</para>
/// <para>When true, getting the property value returns the edited value by default, but
/// it is possible to get the published value using the appropriate 'published' method
/// parameter.</para>
/// <para>When false, getting the property value always return the edited value,
/// regardless of the 'published' method parameter.</para>
/// </remarks>
public bool SupportsPublishing { get; internal set; }
/// <summary>
/// Gets of sets the name of the property type.
@@ -355,18 +368,6 @@ namespace Umbraco.Core.Models
}
// TODO: this and other value validation methods should be a service level (not a model) thing. Changing this to internal for now
/// <summary>
/// Determines whether a value is valid for this property type.
/// </summary>
internal bool IsPropertyValueValid(object value)
{
var editor = Current.PropertyEditors[_propertyEditorAlias]; // TODO: inject
var configuration = Current.Services.DataTypeService.GetDataType(_dataTypeId).Configuration; // TODO: inject
var valueEditor = editor.GetValueEditor(configuration);
return !valueEditor.Validate(value, Mandatory, ValidationRegExp).Any();
}
/// <summary>
/// Sanitizes a property type alias.
/// </summary>
@@ -23,18 +23,18 @@ namespace Umbraco.Core.Models
[IgnoreDataMember]
internal Action OnAdd;
internal PropertyTypeCollection(bool isPublishing)
internal PropertyTypeCollection(bool supportsPublishing)
{
IsPublishing = isPublishing;
SupportsPublishing = supportsPublishing;
}
public PropertyTypeCollection(bool isPublishing, IEnumerable<PropertyType> properties)
: this(isPublishing)
public PropertyTypeCollection(bool supportsPublishing, IEnumerable<PropertyType> properties)
: this(supportsPublishing)
{
Reset(properties);
}
public bool IsPublishing { get; }
public bool SupportsPublishing { get; }
/// <summary>
/// Resets the collection to only contain the <see cref="PropertyType"/> instances referenced in the <paramref name="properties"/> parameter.
@@ -51,7 +51,7 @@ namespace Umbraco.Core.Models
protected override void SetItem(int index, PropertyType item)
{
item.IsPublishing = IsPublishing;
item.SupportsPublishing = SupportsPublishing;
base.SetItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
}
@@ -65,7 +65,7 @@ namespace Umbraco.Core.Models
protected override void InsertItem(int index, PropertyType item)
{
item.IsPublishing = IsPublishing;
item.SupportsPublishing = SupportsPublishing;
base.InsertItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
@@ -79,7 +79,7 @@ namespace Umbraco.Core.Models
// TODO: Instead of 'new' this should explicitly implement one of the collection interfaces members
internal new void Add(PropertyType item)
{
item.IsPublishing = IsPublishing;
item.SupportsPublishing = SupportsPublishing;
// TODO: this is not pretty and should be refactored
try
@@ -155,7 +155,7 @@ namespace Umbraco.Core.Models
public object DeepClone()
{
var clone = new PropertyTypeCollection(IsPublishing);
var clone = new PropertyTypeCollection(SupportsPublishing);
foreach (var propertyType in this)
clone.Add((PropertyType) propertyType.DeepClone());
return clone;
@@ -0,0 +1,18 @@
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Provides a live published model creation service.
/// </summary>
public interface ILivePublishedModelFactory : IPublishedModelFactory
{
/// <summary>
/// Gets an object that can be used to synchronize access to the factory.
/// </summary>
object SyncRoot { get; }
/// <summary>
/// Refreshes the factory.
/// </summary>
void Refresh();
}
}
@@ -11,14 +11,14 @@ namespace Umbraco.Core.Models.PublishedContent
/// <summary>
/// Initializes a new instance of the <see cref="PublishedCultureInfo"/> class.
/// </summary>
public PublishedCultureInfo(string culture, string name, DateTime date)
public PublishedCultureInfo(string culture, string name, string urlSegment, DateTime date)
{
if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentNullOrEmptyException(nameof(culture));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name));
Culture = culture;
Name = name;
UrlSegment = name.ToUrlSegment(culture);
UrlSegment = urlSegment;
Date = date;
}
@@ -78,7 +78,7 @@ namespace Umbraco.Core.Persistence.Factories
var propertyGroups = new PropertyGroupCollection();
foreach (var groupDto in dto.PropertyTypeGroups.Where(x => x.Id.HasValue))
{
var group = new PropertyGroup(MemberType.IsPublishingConst);
var group = new PropertyGroup(MemberType.SupportsPublishingConst);
// if the group is defined on the current member type,
// assign its identifier, else it will be zero
@@ -93,7 +93,7 @@ namespace Umbraco.Core.Persistence.Factories
group.Key = groupDto.UniqueId;
group.Name = groupDto.Text;
group.SortOrder = groupDto.SortOrder;
group.PropertyTypes = new PropertyTypeCollection(MemberType.IsPublishingConst);
group.PropertyTypes = new PropertyTypeCollection(MemberType.SupportsPublishingConst);
//Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded
var localGroupDto = groupDto;
@@ -121,7 +121,7 @@ namespace Umbraco.Core.Persistence.Factories
foreach (var property in properties)
{
if (property.PropertyType.IsPublishing)
if (property.PropertyType.SupportsPublishing)
{
//create the resulting hashset if it's not created and the entity varies by culture
if (entityVariesByCulture && editedCultures == null)
@@ -4,10 +4,7 @@ using System.Data;
using System.Data.SqlClient;
using System.Data.SqlServerCe;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NPoco;
using StackExchange.Profiling.Data;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence
@@ -147,8 +147,23 @@ namespace Umbraco.Core.Persistence
private static TConnection GetTypedConnection<TConnection>(IDbConnection connection)
where TConnection : class, IDbConnection
{
var profiled = connection as ProfiledDbConnection;
return profiled == null ? connection as TConnection : profiled.WrappedConnection as TConnection;
var c = connection;
for (;;)
{
switch (c)
{
case TConnection ofType:
return ofType;
case RetryDbConnection retry:
c = retry.Inner;
break;
case ProfiledDbConnection profiled:
c = profiled.WrappedConnection;
break;
default:
throw new NotSupportedException(connection.GetType().FullName);
}
}
}
/// <summary>
@@ -160,8 +175,20 @@ namespace Umbraco.Core.Persistence
private static TTransaction GetTypedTransaction<TTransaction>(IDbTransaction transaction)
where TTransaction : class, IDbTransaction
{
var profiled = transaction as ProfiledDbTransaction;
return profiled == null ? transaction as TTransaction : profiled.WrappedTransaction as TTransaction;
var t = transaction;
for (;;)
{
switch (t)
{
case TTransaction ofType:
return ofType;
case ProfiledDbTransaction profiled:
t = profiled.WrappedTransaction;
break;
default:
throw new NotSupportedException(transaction.GetType().FullName);
}
}
}
/// <summary>
@@ -173,11 +200,23 @@ namespace Umbraco.Core.Persistence
private static TCommand GetTypedCommand<TCommand>(IDbCommand command)
where TCommand : class, IDbCommand
{
var faultHandling = command as FaultHandlingDbCommand;
if (faultHandling != null) command = faultHandling.Inner;
var profiled = command as ProfiledDbCommand;
if (profiled != null) command = profiled.InternalCommand;
return command as TCommand;
var c = command;
for (;;)
{
switch (c)
{
case TCommand ofType:
return ofType;
case FaultHandlingDbCommand faultHandling:
c = faultHandling.Inner;
break;
case ProfiledDbCommand profiled:
c = profiled.InternalCommand;
break;
default:
throw new NotSupportedException(command.GetType().FullName);
}
}
}
public static void TruncateTable(this IDatabase db, ISqlSyntaxProvider sqlSyntax, string tableName)
@@ -5,6 +5,7 @@ using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using NPoco;
using Umbraco.Core.Persistence.Querying;
@@ -1119,6 +1120,25 @@ namespace Umbraco.Core.Persistence
#endregion
#region Aliasing
internal static string GetAliasedField(this Sql<ISqlContext> sql, string field)
{
// get alias, if aliased
//
// regex looks for pattern "([\w+].[\w+]) AS ([\w+])" ie "(field) AS (alias)"
// and, if found & a group's field matches the field name, returns the alias
//
// so... if query contains "[umbracoNode].[nodeId] AS [umbracoNode__nodeId]"
// then GetAliased for "[umbracoNode].[nodeId]" returns "[umbracoNode__nodeId]"
var matches = sql.SqlContext.SqlSyntax.AliasRegex.Matches(sql.SQL);
var match = matches.Cast<Match>().FirstOrDefault(m => m.Groups[1].Value.InvariantEquals(field));
return match == null ? field : match.Groups[2].Value;
}
#endregion
#region Utilities
private static string[] GetColumns<TDto>(this Sql<ISqlContext> sql, string tableAlias = null, string referenceName = null, Expression<Func<TDto, object>>[] columnExpressions = null, bool withAlias = true)
@@ -25,7 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
_templateRepository = templateRepository;
}
protected override bool IsPublishing => ContentType.IsPublishingConst;
protected override bool SupportsPublishing => ContentType.SupportsPublishingConst;
protected override IRepositoryCachePolicy<IContentType, int> CreateCachePolicy()
{
@@ -60,11 +60,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (ids.Any())
{
//NOTE: This logic should never be executed according to our cache policy
return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, IsPublishing, this, _templateRepository)
return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, SupportsPublishing, this, _templateRepository)
.Where(x => ids.Contains(x.Id));
}
return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, IsPublishing, this, _templateRepository);
return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, SupportsPublishing, this, _templateRepository);
}
protected override IEnumerable<IContentType> PerformGetAll(params Guid[] ids)
@@ -32,7 +32,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
: base(scopeAccessor, cache, logger)
{ }
protected abstract bool IsPublishing { get; }
protected abstract bool SupportsPublishing { get; }
public IEnumerable<MoveEventInfo<TEntity>> Move(TEntity moving, EntityContainer container)
{
@@ -1021,7 +1021,7 @@ AND umbracoNode.id <> @id",
var dtos = Database
.Fetch<PropertyTypeGroupDto>(sql);
var propertyGroups = PropertyGroupFactory.BuildEntity(dtos, IsPublishing, id, createDate, updateDate,CreatePropertyType);
var propertyGroups = PropertyGroupFactory.BuildEntity(dtos, SupportsPublishing, id, createDate, updateDate,CreatePropertyType);
return new PropertyGroupCollection(propertyGroups);
}
@@ -1057,7 +1057,7 @@ AND umbracoNode.id <> @id",
//Reset dirty properties
Parallel.ForEach(list, currentFile => currentFile.ResetDirtyProperties(false));
return new PropertyTypeCollection(IsPublishing, list);
return new PropertyTypeCollection(SupportsPublishing, list);
}
protected void ValidateAlias(PropertyType pt)
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
: base(scopeAccessor, cache, logger)
{ }
protected override bool IsPublishing => MediaType.IsPublishingConst;
protected override bool SupportsPublishing => MediaType.SupportsPublishingConst;
protected override IRepositoryCachePolicy<IMediaType, int> CreateCachePolicy()
{
@@ -55,11 +55,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (ids.Any())
{
//NOTE: This logic should never be executed according to our cache policy
return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, IsPublishing, this)
return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, SupportsPublishing, this)
.Where(x => ids.Contains(x.Id));
}
return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, IsPublishing, this);
return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, SupportsPublishing, this);
}
protected override IEnumerable<IMediaType> PerformGetAll(params Guid[] ids)
@@ -21,7 +21,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
: base(scopeAccessor, cache, logger)
{ }
protected override bool IsPublishing => MemberType.IsPublishingConst;
protected override bool SupportsPublishing => MemberType.SupportsPublishingConst;
protected override IRepositoryCachePolicy<IMemberType, int> CreateCachePolicy()
{
@@ -702,23 +702,6 @@ ORDER BY colName";
{
if (orderBy == null) throw new ArgumentNullException(nameof(orderBy));
// get the referenced column name and find the corresp mapped column name
var expressionMember = ExpressionHelper.GetMemberInfo(orderBy);
var mapper = _mapperCollection[typeof(IUser)];
var mappedField = mapper.Map(SqlContext.SqlSyntax, expressionMember.Name);
if (mappedField.IsNullOrWhiteSpace())
throw new ArgumentException("Could not find a mapping for the column specified in the orderBy clause");
return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, mappedField, orderDirection, includeUserGroups, excludeUserGroups, userState, filter);
}
private IEnumerable<IUser> GetPagedResultsByQuery(IQuery<IUser> query, long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection = Direction.Ascending,
string[] includeUserGroups = null, string[] excludeUserGroups = null, UserState[] userState = null, IQuery<IUser> filter = null)
{
if (string.IsNullOrWhiteSpace(orderBy)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(orderBy));
Sql<ISqlContext> filterSql = null;
var customFilterWheres = filter?.GetWhereClauses().ToArray();
var hasCustomFilter = customFilterWheres != null && customFilterWheres.Length > 0;
@@ -734,7 +717,6 @@ ORDER BY colName";
filterSql.Append($"AND ({clause.Item1})", clause.Item2);
}
if (includeUserGroups != null && includeUserGroups.Length > 0)
{
const string subQuery = @"AND (umbracoUser.id IN (SELECT DISTINCT umbracoUser.id
@@ -808,7 +790,7 @@ ORDER BY colName";
sql = new SqlTranslator<IUser>(sql, query).Translate();
// get sorted and filtered sql
var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql, query != null), orderDirection, orderBy);
var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql, query != null), orderBy, orderDirection);
// get a page of results and total count
var pagedResult = Database.Page<UserDto>(pageIndex + 1, pageSize, sqlNodeIdsWithSort);
@@ -834,14 +816,36 @@ ORDER BY colName";
return sql;
}
private static Sql<ISqlContext> ApplySort(Sql<ISqlContext> sql, Direction orderDirection, string orderBy)
private Sql<ISqlContext> ApplySort(Sql<ISqlContext> sql, Expression<Func<IUser, object>> orderBy, Direction orderDirection)
{
if (string.IsNullOrEmpty(orderBy)) return sql;
if (orderBy == null) return sql;
var expressionMember = ExpressionHelper.GetMemberInfo(orderBy);
var mapper = _mapperCollection[typeof(IUser)];
var mappedField = mapper.Map(SqlContext.SqlSyntax, expressionMember.Name);
if (mappedField.IsNullOrWhiteSpace())
throw new ArgumentException("Could not find a mapping for the column specified in the orderBy clause");
// beware! NPoco paging code parses the query to isolate the ORDER BY fragment,
// using a regex that wants "([\w\.\[\]\(\)\s""`,]+)" - meaning that anything
// else in orderBy is going to break NPoco / not be detected
// beware! NPoco paging code (in PagingHelper) collapses everything [foo].[bar]
// to [bar] only, so we MUST use aliases, cannot use [table].[field]
// beware! pre-2012 SqlServer is using a convoluted syntax for paging, which
// includes "SELECT ROW_NUMBER() OVER (ORDER BY ...) poco_rn FROM SELECT (...",
// so anything added here MUST also be part of the inner SELECT statement, ie
// the original statement, AND must be using the proper alias, as the inner SELECT
// will hide the original table.field names entirely
var orderByField = sql.GetAliasedField(mappedField);
if (orderDirection == Direction.Ascending)
sql.OrderBy(orderBy);
sql.OrderBy(orderByField);
else
sql.OrderByDescending(orderBy);
sql.OrderByDescending(orderByField);
return sql;
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using NPoco;
@@ -122,6 +123,12 @@ namespace Umbraco.Core.Persistence.SqlSyntax
SERVERPROPERTY('ResourceLastUpdateDateTime') ResourceLastUpdateDateTime,
SERVERPROPERTY('ProductLevel') ProductLevel;";
string GetString(IDataReader reader, int ordinal, string defaultValue)
=> reader.IsDBNull(ordinal) ? defaultValue : reader.GetString(ordinal);
int GetInt32(IDataReader reader, int ordinal, int defaultValue)
=> reader.IsDBNull(ordinal) ? defaultValue : reader.GetInt32(ordinal);
connection.ConnectionString = connectionString;
ServerVersionInfo version;
using (connection)
@@ -136,12 +143,12 @@ namespace Umbraco.Core.Persistence.SqlSyntax
reader.Read();
// InstanceName can be NULL for the default instance
version = new ServerVersionInfo(
reader.GetString(0),
reader.IsDBNull(2) ? "(default)" : reader.GetString(2),
reader.IsDBNull(3) ? "" : reader.GetString(3),
(EngineEdition) reader.GetInt32(5),
reader.GetString(7),
reader.GetString(9));
GetString(reader, 0, "Unknown"),
GetString(reader, 2, "(default)"),
GetString(reader, 3, string.Empty),
(EngineEdition) GetInt32(reader, 5, 0),
GetString(reader, 7, "DEFAULT"),
GetString(reader, 9, "Unknown"));
}
connection.Close();
}
@@ -290,7 +290,7 @@ namespace Umbraco.Core.PropertyEditors
/// </summary>
public IEnumerable<XElement> ConvertDbToXml(Property property, IDataTypeService dataTypeService, ILocalizationService localizationService, bool published)
{
published &= property.PropertyType.IsPublishing;
published &= property.PropertyType.SupportsPublishing;
var nodeName = property.PropertyType.Alias.ToSafeAlias();
@@ -36,6 +36,10 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
if (source is int)
return (int)source == 1;
// this is required for correct true/false handling in nested content elements
if (source is long)
return (long)source == 1;
if (source is bool)
return (bool)source;
@@ -0,0 +1,33 @@
using System;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods for <see cref="IPublishedModelFactory"/>.
/// </summary>
public static class PublishedModelFactoryExtensions
{
/// <summary>
/// Executes an action with a safe live factory/
/// </summary>
/// <remarks>
/// <para>If the factory is a live factory, ensures it is refreshed and locked while executing the action.</para>
/// </remarks>
public static void WithSafeLiveFactory(this IPublishedModelFactory factory, Action action)
{
if (factory is ILivePublishedModelFactory liveFactory)
{
lock (liveFactory.SyncRoot)
{
liveFactory.Refresh();
action();
}
}
else
{
action();
}
}
}
}
@@ -5,11 +5,11 @@ using Umbraco.Core.IO;
namespace Umbraco.Core.Runtime
{
public class CoreRuntimeComponent : IComponent
public class CoreInitialComponent : IComponent
{
private readonly IEnumerable<Profile> _mapperProfiles;
public CoreRuntimeComponent(IEnumerable<Profile> mapperProfiles)
public CoreInitialComponent(IEnumerable<Profile> mapperProfiles)
{
_mapperProfiles = mapperProfiles;
}
@@ -24,7 +24,9 @@ using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidato
namespace Umbraco.Core.Runtime
{
public class CoreRuntimeComposer : ComponentComposer<CoreRuntimeComponent>, IRuntimeComposer
// core's initial composer composes before all core composers
[ComposeBefore(typeof(ICoreComposer))]
public class CoreInitialComposer : ComponentComposer<CoreInitialComponent>
{
public override void Compose(Composition composition)
{
+2 -2
View File
@@ -168,7 +168,7 @@ namespace Umbraco.Core.Runtime
_state.BootFailedException = bfe;
}
timer.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves
timer?.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves
// if something goes wrong above, we may end up with no factory
// meaning nothing can get the runtime state, etc - so let's try
@@ -242,7 +242,7 @@ namespace Umbraco.Core.Runtime
}
catch
{
timer.Fail();
timer?.Fail();
throw;
}
}
+2 -2
View File
@@ -75,7 +75,7 @@ namespace Umbraco.Core.Scoping
// see note below
if (scopeFileSystems == true)
_fscope = fileSystems.Shadow(Guid.NewGuid());
_fscope = fileSystems.Shadow();
return;
}
@@ -105,7 +105,7 @@ namespace Umbraco.Core.Scoping
// every scoped FS to trigger the creation of shadow FS "on demand", and that would be
// pretty pointless since if scopeFileSystems is true, we *know* we want to shadow
if (scopeFileSystems == true)
_fscope = fileSystems.Shadow(Guid.NewGuid());
_fscope = fileSystems.Shadow();
}
}
@@ -31,16 +31,16 @@ namespace Umbraco.Core.Services
/// <param name="contentService"></param>
/// <param name="name"></param>
/// <param name="parentId"></param>
/// <param name="mediaTypeAlias"></param>
/// <param name="contentTypeAlias"></param>
/// <param name="userId"></param>
/// <returns></returns>
public static IContent CreateContent(this IContentService contentService, string name, Udi parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId)
public static IContent CreateContent(this IContentService contentService, string name, Udi parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId)
{
var guidUdi = parentId as GuidUdi;
if (guidUdi == null)
throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content");
var parent = contentService.GetById(guidUdi.Guid);
return contentService.Create(name, parent, mediaTypeAlias, userId);
return contentService.Create(name, parent, contentTypeAlias, userId);
}
/// <summary>
+24 -24
View File
@@ -391,30 +391,30 @@ namespace Umbraco.Core.Services
/// </remarks>
IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = Constants.Security.SuperUserId);
/// <summary>
/// Saves and publishes a document branch.
/// </summary>
/// <param name="content">The root document.</param>
/// <param name="force">A value indicating whether to force-publish documents that are not already published.</param>
/// <param name="shouldPublish">A function determining cultures to publish.</param>
/// <param name="publishCultures">A function publishing cultures.</param>
/// <param name="userId">The identifier of the user performing the operation.</param>
/// <remarks>
/// <para>The <paramref name="force"/> parameter determines which documents are published. When <c>false</c>,
/// only those documents that are already published, are republished. When <c>true</c>, all documents are
/// published. The root of the branch is always published, regardless of <paramref name="force"/>.</para>
/// <para>The <paramref name="editing"/> parameter is a function which determines whether a document has
/// changes to publish (else there is no need to publish it). If one wants to publish only a selection of
/// cultures, one may want to check that only properties for these cultures have changed. Otherwise, other
/// cultures may trigger an unwanted republish.</para>
/// <para>The <paramref name="publishCultures"/> parameter is a function to execute to publish cultures, on
/// each document. It can publish all, one, or a selection of cultures. It returns a boolean indicating
/// whether the cultures could be published.</para>
/// </remarks>
IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force,
Func<IContent, HashSet<string>> shouldPublish,
Func<IContent, HashSet<string>, bool> publishCultures,
int userId = Constants.Security.SuperUserId);
///// <summary>
///// Saves and publishes a document branch.
///// </summary>
///// <param name="content">The root document.</param>
///// <param name="force">A value indicating whether to force-publish documents that are not already published.</param>
///// <param name="shouldPublish">A function determining cultures to publish.</param>
///// <param name="publishCultures">A function publishing cultures.</param>
///// <param name="userId">The identifier of the user performing the operation.</param>
///// <remarks>
///// <para>The <paramref name="force"/> parameter determines which documents are published. When <c>false</c>,
///// only those documents that are already published, are republished. When <c>true</c>, all documents are
///// published. The root of the branch is always published, regardless of <paramref name="force"/>.</para>
///// <para>The <paramref name="editing"/> parameter is a function which determines whether a document has
///// changes to publish (else there is no need to publish it). If one wants to publish only a selection of
///// cultures, one may want to check that only properties for these cultures have changed. Otherwise, other
///// cultures may trigger an unwanted republish.</para>
///// <para>The <paramref name="publishCultures"/> parameter is a function to execute to publish cultures, on
///// each document. It can publish all, one, or a selection of cultures. It returns a boolean indicating
///// whether the cultures could be published.</para>
///// </remarks>
//IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force,
// Func<IContent, HashSet<string>> shouldPublish,
// Func<IContent, HashSet<string>, bool> publishCultures,
// int userId = Constants.Security.SuperUserId);
/// <summary>
/// Unpublishes a document.
@@ -10,6 +10,7 @@ using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services.Changes;
@@ -27,6 +28,8 @@ namespace Umbraco.Core.Services.Implement
private readonly IDocumentBlueprintRepository _documentBlueprintRepository;
private readonly ILanguageRepository _languageRepository;
private IQuery<IContent> _queryNotTrashed;
//TODO: The non-lazy object should be injected
private readonly Lazy<PropertyValidationService> _propertyValidationService = new Lazy<PropertyValidationService>(() => new PropertyValidationService());
#region Constructors
@@ -193,7 +196,7 @@ namespace Umbraco.Core.Services.Implement
var content = new Content(name, parentId, contentType);
using (var scope = ScopeProvider.CreateScope())
{
CreateContent(scope, content, parent, userId, false);
CreateContent(scope, content, userId, false);
scope.Complete();
}
@@ -227,7 +230,7 @@ namespace Umbraco.Core.Services.Implement
throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback
var content = new Content(name, parent, contentType);
CreateContent(scope, content, parent, userId, false);
CreateContent(scope, content, userId, false);
scope.Complete();
return content;
@@ -261,7 +264,7 @@ namespace Umbraco.Core.Services.Implement
throw new ArgumentException("No content with that id.", nameof(parentId)); // causes rollback
var content = parentId > 0 ? new Content(name, parent, contentType) : new Content(name, parentId, contentType);
CreateContent(scope, content, parent, userId, true);
CreateContent(scope, content, userId, true);
scope.Complete();
return content;
@@ -293,14 +296,14 @@ namespace Umbraco.Core.Services.Implement
throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback
var content = new Content(name, parent, contentType);
CreateContent(scope, content, parent, userId, true);
CreateContent(scope, content, userId, true);
scope.Complete();
return content;
}
}
private void CreateContent(IScope scope, Content content, IContent parent, int userId, bool withIdentity)
private void CreateContent(IScope scope, IContent content, int userId, bool withIdentity)
{
content.CreatorId = userId;
content.WriterId = userId;
@@ -311,12 +314,12 @@ namespace Umbraco.Core.Services.Implement
// if saving is cancelled, content remains without an identity
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return;
_documentRepository.Save(content);
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), "Saved");
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved));
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, TreeChangeTypes.RefreshNode).ToEventArgs());
}
@@ -758,7 +761,7 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
{
scope.Complete();
return OperationResult.Cancel(evtMsgs);
@@ -783,7 +786,7 @@ namespace Umbraco.Core.Services.Implement
if (raiseEvents)
{
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), "Saved");
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved));
}
var changeType = TreeChangeTypes.RefreshNode;
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, changeType).ToEventArgs());
@@ -813,7 +816,7 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new ContentSavingEventArgs(contentsA, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
{
scope.Complete();
return OperationResult.Cancel(evtMsgs);
@@ -833,7 +836,7 @@ namespace Umbraco.Core.Services.Implement
if (raiseEvents)
{
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), "Saved");
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved));
}
scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs());
Audit(AuditType.Save, userId == -1 ? 0 : userId, Constants.System.Root, "Saved multiple content");
@@ -866,26 +869,51 @@ namespace Umbraco.Core.Services.Implement
throw new NotSupportedException($"Culture \"{culture}\" is not supported by invariant content types.");
}
// if culture is specific, first publish the invariant values, then publish the culture itself.
// if culture is '*', then publish them all (including variants)
// explicitly SaveAndPublish a specific culture also publishes invariant values
if (!culture.IsNullOrWhiteSpace() && culture != "*")
using (var scope = ScopeProvider.CreateScope())
{
// publish the invariant values
var publishInvariant = content.PublishCulture(null);
if (!publishInvariant)
scope.WriteLock(Constants.Locks.ContentTree);
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
Property[] invalidProperties;
// if culture is specific, first publish the invariant values, then publish the culture itself.
// if culture is '*', then publish them all (including variants)
// explicitly SaveAndPublish a specific culture also publishes invariant values
if (!culture.IsNullOrWhiteSpace() && culture != "*")
{
// publish the invariant values
var publishInvariant = content.PublishCulture(null);
if (!publishInvariant)
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
//validate the property values
if (!_propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties))
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
{
InvalidProperties = invalidProperties
};
}
// publish the culture(s)
var publishCulture = content.PublishCulture(culture);
if (!publishCulture)
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
//validate the property values
if (!_propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties))
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
{
InvalidProperties = invalidProperties
};
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
scope.Complete();
return result;
}
// publish the culture(s)
var publishCulture = content.PublishCulture(culture);
if (!publishCulture)
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
// finally, "save publishing"
// what happens next depends on whether the content can be published or not
return CommitDocumentChanges(content, userId, raiseEvents);
}
/// <inheritdoc />
@@ -894,23 +922,40 @@ namespace Umbraco.Core.Services.Implement
if (content == null) throw new ArgumentNullException(nameof(content));
if (cultures == null) throw new ArgumentNullException(nameof(cultures));
var evtMsgs = EventMessagesFactory.Get();
var varies = content.ContentType.VariesByCulture();
if (cultures.Length == 0)
using (var scope = ScopeProvider.CreateScope())
{
//no cultures specified and doesn't vary, so publish it, else nothing to publish
return !varies
? SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents)
: new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
scope.WriteLock(Constants.Locks.ContentTree);
var evtMsgs = EventMessagesFactory.Get();
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
var varies = content.ContentType.VariesByCulture();
if (cultures.Length == 0)
{
//no cultures specified and doesn't vary, so publish it, else nothing to publish
return !varies
? SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents)
: new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
}
if (cultures.Select(content.PublishCulture).Any(isValid => !isValid))
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
//validate the property values
if (!_propertyValidationService.Value.IsPropertyDataValid(content, out var invalidProperties))
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
{
InvalidProperties = invalidProperties
};
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
scope.Complete();
return result;
}
// TODO: currently, no way to know which one failed
if (cultures.Select(content.PublishCulture).Any(isValid => !isValid))
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
return CommitDocumentChanges(content, userId, raiseEvents);
}
/// <inheritdoc />
@@ -943,56 +988,87 @@ namespace Umbraco.Core.Services.Implement
if (!content.Published)
return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
// all cultures = unpublish whole
if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
using (var scope = ScopeProvider.CreateScope())
{
content.PublishedState = PublishedState.Unpublishing;
}
else
{
// If the culture we want to unpublish was already unpublished, nothing to do.
// To check for that we need to lookup the persisted content item
var persisted = content.HasIdentity ? GetById(content.Id) : null;
scope.WriteLock(Constants.Locks.ContentTree);
if (persisted != null && !persisted.IsCulturePublished(culture))
return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
// unpublish the culture
content.UnpublishCulture(culture);
// all cultures = unpublish whole
if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
{
content.PublishedState = PublishedState.Unpublishing;
}
else
{
// If the culture we want to unpublish was already unpublished, nothing to do.
// To check for that we need to lookup the persisted content item
var persisted = content.HasIdentity ? GetById(content.Id) : null;
if (persisted != null && !persisted.IsCulturePublished(culture))
return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
// unpublish the culture
content.UnpublishCulture(culture);
}
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId);
scope.Complete();
return result;
}
// finally, "save publishing"
return CommitDocumentChanges(content, userId);
}
/// <summary>
/// Saves a document and publishes/unpublishes any pending publishing changes made to the document.
/// </summary>
/// <remarks>
/// <para>
/// This MUST NOT be called from within this service, this used to be a public API and must only be used outside of this service.
/// Internally in this service, calls must be made to CommitDocumentChangesInternal
/// </para>
///
/// <para>This is the underlying logic for both publishing and unpublishing any document</para>
/// <para>Pending publishing/unpublishing changes on a document are made with calls to <see cref="IContent.PublishCulture"/> and
/// <see cref="IContent.UnpublishCulture"/>.</para>
/// <para>Pending publishing/unpublishing changes on a document are made with calls to <see cref="ContentRepositoryExtensions.PublishCulture"/> and
/// <see cref="ContentRepositoryExtensions.UnpublishCulture"/>.</para>
/// <para>When publishing or unpublishing a single culture, or all cultures, use <see cref="SaveAndPublish"/>
/// and <see cref="Unpublish"/>. But if the flexibility to both publish and unpublish in a single operation is required
/// then this method needs to be used in combination with <see cref="IContent.PublishCulture"/> and <see cref="IContent.UnpublishCulture"/>
/// then this method needs to be used in combination with <see cref="ContentRepositoryExtensions.PublishCulture"/> and <see cref="ContentRepositoryExtensions.UnpublishCulture"/>
/// on the content itself - this prepares the content, but does not commit anything - and then, invoke
/// <see cref="CommitDocumentChanges"/> to actually commit the changes to the database.</para>
/// <para>The document is *always* saved, even when publishing fails.</para>
/// </remarks>
internal PublishResult CommitDocumentChanges(IContent content, int userId = Constants.Security.SuperUserId, bool raiseEvents = true)
internal PublishResult CommitDocumentChanges(IContent content,
int userId = Constants.Security.SuperUserId, bool raiseEvents = true)
{
using (var scope = ScopeProvider.CreateScope())
{
var evtMsgs = EventMessagesFactory.Get();
scope.WriteLock(Constants.Locks.ContentTree);
var result = CommitDocumentChangesInternal(scope, content, userId, raiseEvents);
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
scope.Complete();
return result;
}
}
private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content, int userId = Constants.Security.SuperUserId, bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content,
ContentSavingEventArgs saveEventArgs,
int userId = Constants.Security.SuperUserId, bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
{
var evtMsgs = EventMessagesFactory.Get();
if (scope == null) throw new ArgumentNullException(nameof(scope));
if (content == null) throw new ArgumentNullException(nameof(content));
if (saveEventArgs == null) throw new ArgumentNullException(nameof(saveEventArgs));
var evtMsgs = saveEventArgs.Messages;
PublishResult publishResult = null;
PublishResult unpublishResult = null;
@@ -1018,11 +1094,6 @@ namespace Umbraco.Core.Services.Implement
var changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch;
var previouslyPublished = content.HasIdentity && content.Published;
// always save
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
if (publishing)
{
culturesUnpublishing = content.GetCulturesUnpublishing();
@@ -1058,7 +1129,7 @@ namespace Umbraco.Core.Services.Implement
// but: (a) it means we don't reproduce the PublishState logic here and (b) setting the
// PublishState to anything other than Publishing or Unpublishing - which is precisely
// what we want to do here - throws
content.Published = content.Published;
content.Published = content.Published;
}
}
@@ -1107,7 +1178,7 @@ namespace Umbraco.Core.Services.Implement
// raise the Saved event, always
if (raiseEvents)
{
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), "Saved");
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved));
}
if (unpublishing) // we have tried to unpublish - won't happen in a branch
@@ -1242,6 +1313,13 @@ namespace Umbraco.Core.Services.Implement
.Distinct()
.ToList();
if (pendingCultures.Count == 0)
break; //shouldn't happen but no point in continuing if there's nothing there
var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
yield return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d);
var publishing = true;
foreach (var culture in pendingCultures)
{
@@ -1250,7 +1328,14 @@ namespace Umbraco.Core.Services.Implement
if (d.Trashed) continue; // won't publish
publishing &= d.PublishCulture(culture); //set the culture to be published
//publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed
Property[] invalidProperties = null;
var tryPublish = d.PublishCulture(culture) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties);
if (invalidProperties != null && invalidProperties.Length > 0)
Logger.Warn<ContentService>("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}",
d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias)));
publishing &= tryPublish; //set the culture to be published
if (!publishing) break; // no point continuing
}
@@ -1259,7 +1344,8 @@ namespace Umbraco.Core.Services.Implement
else if (!publishing)
result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d);
else
result = CommitDocumentChanges(d, d.WriterId);
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
if (result.Success == false)
Logger.Error<ContentService>(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
@@ -1293,6 +1379,13 @@ namespace Umbraco.Core.Services.Implement
.Distinct()
.ToList();
if (pendingCultures.Count == 0)
break; //shouldn't happen but no point in continuing if there's nothing there
var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
yield return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d);
foreach (var c in pendingCultures)
{
//Clear this schedule for this culture
@@ -1301,13 +1394,11 @@ namespace Umbraco.Core.Services.Implement
d.UnpublishCulture(c);
}
if (pendingCultures.Count > 0)
{
result = CommitDocumentChanges(d, d.WriterId);
if (result.Success == false)
Logger.Error<ContentService>(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
yield return result;
}
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
if (result.Success == false)
Logger.Error<ContentService>(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
yield return result;
}
else
{
@@ -1328,16 +1419,20 @@ namespace Umbraco.Core.Services.Implement
}
}
private bool SaveAndPublishBranch_PublishCultures(IContent c, HashSet<string> culturesToPublish)
// utility 'PublishCultures' func used by SaveAndPublishBranch
private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet<string> culturesToPublish)
{
//TODO: This does not support being able to return invalid property details to bubble up to the UI
// variant content type - publish specified cultures
// invariant content type - publish only the invariant culture
return c.ContentType.VariesByCulture()
? culturesToPublish.All(c.PublishCulture)
: c.PublishCulture();
return content.ContentType.VariesByCulture()
? culturesToPublish.All(culture => content.PublishCulture(culture) && _propertyValidationService.Value.IsPropertyDataValid(content, out _))
: content.PublishCulture() && _propertyValidationService.Value.IsPropertyDataValid(content, out _);
}
private HashSet<string> SaveAndPublishBranch_ShouldPublish3(ref HashSet<string> cultures, string c, bool published, bool edited, bool isRoot, bool force)
// utility 'ShouldPublish' func used by SaveAndPublishBranch
private HashSet<string> SaveAndPublishBranch_ShouldPublish(ref HashSet<string> cultures, string c, bool published, bool edited, bool isRoot, bool force)
{
// if published, republish
if (published)
@@ -1355,7 +1450,6 @@ namespace Umbraco.Core.Services.Implement
return cultures;
}
/// <inheritdoc />
public IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = Constants.Security.SuperUserId)
{
@@ -1374,10 +1468,10 @@ namespace Umbraco.Core.Services.Implement
HashSet<string> culturesToPublish = null;
if (!c.ContentType.VariesByCulture()) // invariant content type
return SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
if (culture != "*") // variant content type, specific culture
return SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, culture, c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force);
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, culture, c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force);
// variant content type, all cultures
if (c.Published)
@@ -1385,7 +1479,7 @@ namespace Umbraco.Core.Services.Implement
// then some (and maybe all) cultures will be 'already published' (unless forcing),
// others will have to 'republish this culture'
foreach (var x in c.AvailableCultures)
SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force);
SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force);
return culturesToPublish;
}
@@ -1414,7 +1508,7 @@ namespace Umbraco.Core.Services.Implement
HashSet<string> culturesToPublish = null;
if (!c.ContentType.VariesByCulture()) // invariant content type
return SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
// variant content type, specific cultures
if (c.Published)
@@ -1422,7 +1516,7 @@ namespace Umbraco.Core.Services.Implement
// then some (and maybe all) cultures will be 'already published' (unless forcing),
// others will have to 'republish this culture'
foreach (var x in cultures)
SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force);
SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force);
return culturesToPublish;
}
@@ -1435,8 +1529,7 @@ namespace Umbraco.Core.Services.Implement
return SaveAndPublishBranch(content, force, ShouldPublish, SaveAndPublishBranch_PublishCultures, userId);
}
/// <inheritdoc />
public IEnumerable<PublishResult> SaveAndPublishBranch(IContent document, bool force,
internal IEnumerable<PublishResult> SaveAndPublishBranch(IContent document, bool force,
Func<IContent, HashSet<string>> shouldPublish,
Func<IContent, HashSet<string>, bool> publishCultures,
int userId = Constants.Security.SuperUserId)
@@ -1460,7 +1553,7 @@ namespace Umbraco.Core.Services.Implement
throw new InvalidOperationException("Cannot mix PublishCulture and SaveAndPublishBranch.");
// deal with the branch root - if it fails, abort
var result = SaveAndPublishBranchOne(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId);
var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId);
if (result != null)
{
results.Add(result);
@@ -1491,7 +1584,7 @@ namespace Umbraco.Core.Services.Implement
}
// no need to check path here, parent has to be published here
result = SaveAndPublishBranchOne(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId);
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId);
if (result != null)
{
results.Add(result);
@@ -1521,7 +1614,7 @@ namespace Umbraco.Core.Services.Implement
// shouldPublish: a function determining whether the document has changes that need to be published
// note - 'force' is handled by 'editing'
// publishValues: a function publishing values (using the appropriate PublishCulture calls)
private PublishResult SaveAndPublishBranchOne(IScope scope, IContent document,
private PublishResult SaveAndPublishBranchItem(IScope scope, IContent document,
Func<IContent, HashSet<string>> shouldPublish,
Func<IContent, HashSet<string>, bool> publishCultures,
bool isRoot,
@@ -1534,11 +1627,18 @@ namespace Umbraco.Core.Services.Implement
if (culturesToPublish.Count == 0) // empty = already published
return new PublishResult(PublishResultType.SuccessPublishAlready, evtMsgs, document);
var saveEventArgs = new ContentSavingEventArgs(document, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document);
// publish & check if values are valid
if (!publishCultures(document, culturesToPublish))
{
//TODO: Based on this callback behavior there is no way to know which properties may have been invalid if this failed, see other results of FailedPublishContentInvalid
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document);
}
var result = CommitDocumentChangesInternal(scope, document, userId, branchOne: true, branchRoot: isRoot);
var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, userId, branchOne: true, branchRoot: isRoot);
if (result.Success)
publishedDocuments.Add(document);
return result;
@@ -2513,7 +2613,6 @@ namespace Umbraco.Core.Services.Implement
return new PublishResult(PublishResultType.SuccessPublishCulture, evtMsgs, content);
}
Logger.Info<ContentService>("Document {ContentName} (id={ContentId}) has been published.", content.Name, content.Id);
return new PublishResult(evtMsgs, content);
}
@@ -1175,7 +1175,7 @@ namespace Umbraco.Core.Services.Implement
var identity = int.MaxValue;
var memType = new MemberType(-1);
var propGroup = new PropertyGroup(MemberType.IsPublishingConst)
var propGroup = new PropertyGroup(MemberType.SupportsPublishingConst)
{
Name = "Membership",
Id = --identity
@@ -3,26 +3,14 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using System.Xml.Linq;
using Semver;
using Umbraco.Core.Collections;
using Umbraco.Core.Events;
using Umbraco.Core.Exceptions;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Packaging;
using Umbraco.Core.Packaging;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Strings;
using Content = Umbraco.Core.Models.Content;
namespace Umbraco.Core.Services.Implement
{
@@ -0,0 +1,135 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core.Collections;
using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Core.Services
{
//TODO: We should make this an interface and inject it into the ContentService
internal class PropertyValidationService
{
private readonly PropertyEditorCollection _propertyEditors;
private readonly IDataTypeService _dataTypeService;
public PropertyValidationService(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService)
{
_propertyEditors = propertyEditors;
_dataTypeService = dataTypeService;
}
//TODO: Remove this method in favor of the overload specifying all dependencies
public PropertyValidationService()
: this(Current.PropertyEditors, Current.Services.DataTypeService)
{
}
/// <summary>
/// Validates the content item's properties pass validation rules
/// </summary>
/// <para>If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor
/// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty.</para>
public bool IsPropertyDataValid(IContentBase content, out Property[] invalidProperties, string culture = "*")
{
// select invalid properties
invalidProperties = content.Properties.Where(x =>
{
// if culture is null, we validate invariant properties only
// if culture is '*' we validate both variant and invariant properties, automatically
// if culture is specific eg 'en-US' we both too, but explicitly
var varies = x.PropertyType.VariesByCulture();
if (culture == null)
return !(varies || IsPropertyValid(x, null)); // validate invariant property, invariant culture
if (culture == "*")
return !IsPropertyValid(x, culture); // validate property, all cultures
return varies
? !IsPropertyValid(x, culture) // validate variant property, explicit culture
: !IsPropertyValid(x, null); // validate invariant property, explicit culture
})
.ToArray();
return invalidProperties.Length == 0;
}
/// <summary>
/// Gets a value indicating whether the property has valid values.
/// </summary>
public bool IsPropertyValid(Property property, string culture = "*", string segment = "*")
{
//NOTE - the pvalue and vvalues logic in here is borrowed directly from the Property.Values setter so if you are wondering what that's all about, look there.
// The underlying Property._pvalue and Property._vvalues are not exposed but we can re-create these values ourselves which is what it's doing.
culture = culture.NullOrWhiteSpaceAsNull();
segment = segment.NullOrWhiteSpaceAsNull();
Property.PropertyValue pvalue = null;
// if validating invariant/neutral, and it is supported, validate
// (including ensuring that the value exists, if mandatory)
if ((culture == null || culture == "*") && (segment == null || segment == "*") && property.PropertyType.SupportsVariation(null, null))
{
// validate pvalue (which is the invariant value)
pvalue = property.Values.FirstOrDefault(x => x.Culture == null && x.Segment == null);
if (!IsValidPropertyValue(property, pvalue?.EditedValue))
return false;
}
// if validating only invariant/neutral, we are good
if (culture == null && segment == null)
return true;
// if nothing else to validate, we are good
if ((culture == null || culture == "*") && (segment == null || segment == "*") && !property.PropertyType.VariesByCulture())
return true;
// for anything else, validate the existing values (including mandatory),
// but we cannot validate mandatory globally (we don't know the possible cultures and segments)
// validate vvalues (which are the variant values)
// if we don't have vvalues (property.Values is empty or only contains pvalue), validate null
if (property.Values.Count == (pvalue == null ? 0 : 1))
return culture == "*" || IsValidPropertyValue(property, null);
// else validate vvalues (but don't revalidate pvalue)
var pvalues = property.Values.Where(x =>
x != pvalue && // don't revalidate pvalue
property.PropertyType.SupportsVariation(x.Culture, x.Segment, true) && // the value variation is ok
(culture == "*" || x.Culture.InvariantEquals(culture)) && // the culture matches
(segment == "*" || x.Segment.InvariantEquals(segment))) // the segment matches
.ToList();
return pvalues.Count == 0 || pvalues.All(x => IsValidPropertyValue(property, x.EditedValue));
}
/// <summary>
/// Boolean indicating whether the passed in value is valid
/// </summary>
/// <param name="property"></param>
/// <param name="value"></param>
/// <returns>True is property value is valid, otherwise false</returns>
private bool IsValidPropertyValue(Property property, object value)
{
return IsPropertyValueValid(property.PropertyType, value);
}
/// <summary>
/// Determines whether a value is valid for this property type.
/// </summary>
private bool IsPropertyValueValid(PropertyType propertyType, object value)
{
var editor = _propertyEditors[propertyType.PropertyEditorAlias];
var configuration = _dataTypeService.GetDataType(propertyType.DataTypeId).Configuration;
var valueEditor = editor.GetValueEditor(configuration);
return !valueEditor.Validate(value, propertyType.Mandatory, propertyType.ValidationRegExp).Any();
}
}
}
+47 -62
View File
@@ -593,13 +593,14 @@ namespace Umbraco.Core
///<returns></returns>
public static string ToUrlBase64(this string input)
{
if (input == null) throw new ArgumentNullException("input");
if (input == null) throw new ArgumentNullException(nameof(input));
if (String.IsNullOrEmpty(input)) return String.Empty;
if (string.IsNullOrEmpty(input))
return string.Empty;
//return Convert.ToBase64String(bytes).Replace(".", "-").Replace("/", "_").Replace("=", ",");
var bytes = Encoding.UTF8.GetBytes(input);
return UrlTokenEncode(bytes);
//return Convert.ToBase64String(bytes).Replace(".", "-").Replace("/", "_").Replace("=", ",");
}
/// <summary>
@@ -609,14 +610,14 @@ namespace Umbraco.Core
/// <returns></returns>
public static string FromUrlBase64(this string input)
{
if (input == null) throw new ArgumentNullException("input");
if (input == null) throw new ArgumentNullException(nameof(input));
//if (input.IsInvalidBase64()) return null;
try
{
//var decodedBytes = Convert.FromBase64String(input.Replace("-", ".").Replace("_", "/").Replace(",", "="));
byte[] decodedBytes = UrlTokenDecode(input);
var decodedBytes = UrlTokenDecode(input);
return decodedBytes != null ? Encoding.UTF8.GetString(decodedBytes) : null;
}
catch (FormatException)
@@ -795,42 +796,40 @@ namespace Umbraco.Core
internal static byte[] UrlTokenDecode(string input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
if (input.Length == 0)
return Array.Empty<byte>();
// calc array size - must be groups of 4
var arrayLength = input.Length;
var remain = arrayLength % 4;
if (remain != 0) arrayLength += 4 - remain;
var inArray = new char[arrayLength];
for (var i = 0; i < input.Length; i++)
{
throw new ArgumentNullException("input");
}
int length = input.Length;
if (length < 1)
{
return new byte[0];
}
int num2 = input[length - 1] - '0';
if ((num2 < 0) || (num2 > 10))
{
return null;
}
char[] inArray = new char[(length - 1) + num2];
for (int i = 0; i < (length - 1); i++)
{
char ch = input[i];
var ch = input[i];
switch (ch)
{
case '-':
case '-': // restore '-' as '+'
inArray[i] = '+';
break;
case '_':
case '_': // restore '_' as '/'
inArray[i] = '/';
break;
default:
default: // keep char unchanged
inArray[i] = ch;
break;
}
}
for (int j = length - 1; j < inArray.Length; j++)
{
// pad with '='
for (var j = input.Length; j < inArray.Length; j++)
inArray[j] = '=';
}
return Convert.FromBase64CharArray(inArray, 0, inArray.Length);
}
@@ -842,54 +841,40 @@ namespace Umbraco.Core
internal static string UrlTokenEncode(byte[] input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
if (input.Length == 0)
return string.Empty;
// base-64 digits are A-Z, a-z, 0-9, + and /
// the = char is used for trailing padding
var str = Convert.ToBase64String(input);
var pos = str.IndexOf('=');
if (pos < 0) pos = str.Length;
// replace chars that would cause problems in urls
var chArray = new char[pos];
for (var i = 0; i < pos; i++)
{
throw new ArgumentNullException("input");
}
if (input.Length < 1)
{
return String.Empty;
}
string str = null;
int index = 0;
char[] chArray = null;
str = Convert.ToBase64String(input);
if (str == null)
{
return null;
}
index = str.Length;
while (index > 0)
{
if (str[index - 1] != '=')
{
break;
}
index--;
}
chArray = new char[index + 1];
chArray[index] = (char)((0x30 + str.Length) - index);
for (int i = 0; i < index; i++)
{
char ch = str[i];
var ch = str[i];
switch (ch)
{
case '+':
case '+': // replace '+' with '-'
chArray[i] = '-';
break;
case '/':
case '/': // replace '/' with '_'
chArray[i] = '_';
break;
case '=':
chArray[i] = ch;
break;
default:
default: // keep char unchanged
chArray[i] = ch;
break;
}
}
return new string(chArray);
}
+11 -4
View File
@@ -161,14 +161,13 @@
<Compile Include="Composing\ComponentComposer.cs" />
<Compile Include="Composing\Composers.cs" />
<Compile Include="Composing\Composition.cs" />
<Compile Include="CompositionExtensions_Accessors.cs" />
<Compile Include="CompositionExtensions.cs" />
<Compile Include="Composing\DisableAttribute.cs" />
<Compile Include="Composing\DisableComposerAttribute.cs" />
<Compile Include="Composing\EnableAttribute.cs" />
<Compile Include="Composing\EnableComposerAttribute.cs" />
<Compile Include="Composing\IComposer.cs" />
<Compile Include="Composing\ICoreComposer.cs" />
<Compile Include="Composing\IRuntimeComposer.cs" />
<Compile Include="Composing\IComponent.cs" />
<Compile Include="Composing\IUserComposer.cs" />
<Compile Include="Compose\ManifestWatcherComposer.cs" />
@@ -187,6 +186,8 @@
<Compile Include="Composing\CompositionExtensions\Repositories.cs" />
<Compile Include="Composing\CompositionExtensions\Services.cs" />
<Compile Include="CompositionExtensions_Essentials.cs" />
<Compile Include="CompositionExtensions_FileSystems.cs" />
<Compile Include="CompositionExtensions_Uniques.cs" />
<Compile Include="FactoryExtensions.cs" />
<Compile Include="Composing\RegisterFactory.cs" />
<Compile Include="Composing\Current.cs" />
@@ -205,9 +206,15 @@
<Compile Include="Composing\TypeFinder.cs" />
<Compile Include="Composing\TypeHelper.cs" />
<Compile Include="Composing\TypeLoader.cs" />
<Compile Include="IO\MediaPathSchemes\UniqueMediaPathScheme.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\MergeDateAndDateTimePropertyEditor.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_1\ChangeNuCacheJsonFormat.cs" />
<Compile Include="Models\PublishedContent\ILivePublishedModelFactory.cs" />
<Compile Include="PropertyEditors\DateTimeConfiguration.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\RenameLabelAndRichTextPropertyEditorAliases.cs" />
<Compile Include="PublishedModelFactoryExtensions.cs" />
<Compile Include="Services\PropertyValidationService.cs" />
<Compile Include="Composing\TypeCollectionBuilderBase.cs" />
<Compile Include="TypeLoaderExtensions.cs" />
<Compile Include="Composing\WeightAttribute.cs" />
<Compile Include="Composing\WeightedCollectionBuilderBase.cs" />
@@ -524,7 +531,7 @@
<Compile Include="RuntimeLevelReason.cs" />
<Compile Include="RuntimeOptions.cs" />
<Compile Include="Runtime\CoreRuntime.cs" />
<Compile Include="Runtime\CoreRuntimeComponent.cs" />
<Compile Include="Runtime\CoreInitialComponent.cs" />
<Compile Include="CustomBooleanTypeConverter.cs" />
<Compile Include="Migrations\Expressions\Common\ExecutableBuilder.cs" />
<Compile Include="Migrations\Expressions\Common\IExecutableBuilder.cs" />
@@ -1305,7 +1312,7 @@
<Compile Include="ReflectionUtilities.cs" />
<Compile Include="RuntimeLevel.cs" />
<Compile Include="RuntimeState.cs" />
<Compile Include="Runtime\CoreRuntimeComposer.cs" />
<Compile Include="Runtime\CoreInitialComposer.cs" />
<Compile Include="SafeCallContext.cs" />
<Compile Include="Scoping\IInstanceIdentifiable.cs" />
<Compile Include="Scoping\IScope.cs" />
@@ -158,7 +158,7 @@ namespace Umbraco.Tests.Cache
new TestDefaultCultureAccessor(),
TestObjects.GetUmbracoSettings(),
TestObjects.GetGlobalSettings(),
Enumerable.Empty<IUrlProvider>(),
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Mock.Of<IUserService>());
// just assert it does not throw
@@ -15,6 +15,14 @@ namespace Umbraco.Tests.CoreThings
Assert.AreEqual(GuidUtils.Combine(a, b).ToByteArray(), Combine(a, b));
}
[Test]
public void GuidThingTest()
{
var guid = new Guid("f918382f-2bba-453f-a3e2-1f594016ed3b");
Assert.AreEqual("f22br4n0fm5fli5c", GuidUtils.ToBase32String(guid, 16));
Assert.AreEqual("f22br4n0f", GuidUtils.ToBase32String(guid, 9));
}
// Reference implementation taken from original code.
private static byte[] Combine(Guid guid1, Guid guid2)
{
+1 -1
View File
@@ -33,7 +33,7 @@ namespace Umbraco.Tests.IO
composition.Register(_ => Mock.Of<ILogger>());
composition.Register(_ => Mock.Of<IDataTypeService>());
composition.Register(_ => Mock.Of<IContentSection>());
composition.RegisterUnique<IMediaPathScheme, OriginalMediaPathScheme>();
composition.RegisterUnique<IMediaPathScheme, UniqueMediaPathScheme>();
composition.Configs.Add(SettingsForTests.GetDefaultGlobalSettings);
composition.Configs.Add(SettingsForTests.GetDefaultUmbracoSettings);
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using NUnit.Framework;
@@ -44,6 +42,34 @@ namespace Umbraco.Tests.IO
return "/Media/" + path;
}
private string Repeat(string pattern, int count)
{
var text = new StringBuilder();
for (var i = 0; i < count; i++)
text.Append(pattern);
return text.ToString();
}
[Test]
public void SaveFileTest()
{
var basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests");
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
_fileSystem.AddFile("sub/f3.txt", ms);
Assert.IsTrue(File.Exists(Path.Combine(basePath, "sub/f3.txt")));
var path = Repeat("bah/bah/", 50);
Assert.Less(260, path.Length);
Assert.Throws<PathTooLongException>(() =>
{
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
_fileSystem.AddFile(path + "f3.txt", ms);
});
}
[Test]
public void GetFullPathTest()
{
@@ -60,14 +86,31 @@ namespace Umbraco.Tests.IO
// - does properly normalize separators
// - does throw on invalid paths
// works
var path = _fileSystem.GetFullPath("foo.tmp");
Assert.AreEqual(Path.Combine(basePath, @"foo.tmp"), path);
// a very long relative path, which ends up being a short path, works
path = Repeat("bah/../", 50);
Assert.Less(260, path.Length);
path = _fileSystem.GetFullPath(path + "foo.tmp");
Assert.AreEqual(Path.Combine(basePath, @"foo.tmp"), path);
// works too
path = _fileSystem.GetFullPath("foo/bar.tmp");
Assert.AreEqual(Path.Combine(basePath, @"foo\bar.tmp"), path);
// that path is invalid as it would be outside the root directory
Assert.Throws<FileSecurityException>(() => _fileSystem.GetFullPath("../../foo.tmp"));
// a very long path, which ends up being very long, works
path = Repeat("bah/bah/", 50);
Assert.Less(260, path.Length);
Assert.Throws<PathTooLongException>(() =>
{
path = _fileSystem.GetFullPath(path + "foo.tmp");
Assert.Less(260, path.Length); // gets a >260 path and it's fine (but Windows will not like it)
});
}
}
}
+12 -11
View File
@@ -405,10 +405,10 @@ namespace Umbraco.Tests.IO
sw.AddFile("sub/f1.txt", ms);
Assert.IsTrue(phy.FileExists("sub/f1.txt"));
Guid id;
string id;
// explicit shadow without scope does not work
sw.Shadow(id = Guid.NewGuid());
sw.Shadow(id = ShadowWrapper.CreateShadowId());
Assert.IsTrue(Directory.Exists(shadowfs + "/" + id));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
sw.AddFile("sub/f2.txt", ms);
@@ -419,7 +419,7 @@ namespace Umbraco.Tests.IO
// shadow with scope but no complete does not complete
scopedFileSystems = true; // pretend we have a scope
var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid());
var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId());
Assert.IsTrue(Directory.Exists(shadowfs + "/" + id));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
sw.AddFile("sub/f3.txt", ms);
@@ -428,10 +428,11 @@ namespace Umbraco.Tests.IO
Assert.AreEqual(1, dirs.Length);
Assert.AreEqual((shadowfs + "/" + id).Replace('\\', '/'), dirs[0].Replace('\\', '/'));
dirs = Directory.GetDirectories(dirs[0]);
var typedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/typed"));
var typedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/x"));
Assert.IsNotNull(typedDir);
dirs = Directory.GetDirectories(typedDir);
var scopedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/Umbraco.Tests.IO.ShadowFileSystemTests+FS")); // this is where files go
var suid = fileSystems.Paths[typeof(FS)];
var scopedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/" + suid)); // this is where files go
Assert.IsNotNull(scopedDir);
scope.Dispose();
scopedFileSystems = false;
@@ -440,7 +441,7 @@ namespace Umbraco.Tests.IO
// shadow with scope and complete does complete
scopedFileSystems = true; // pretend we have a scope
scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid());
scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId());
Assert.IsTrue(Directory.Exists(shadowfs + "/" + id));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
sw.AddFile("sub/f4.txt", ms);
@@ -456,7 +457,7 @@ namespace Umbraco.Tests.IO
// test scope for "another thread"
scopedFileSystems = true; // pretend we have a scope
scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid());
scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId());
Assert.IsTrue(Directory.Exists(shadowfs + "/" + id));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
sw.AddFile("sub/f5.txt", ms);
@@ -498,10 +499,10 @@ namespace Umbraco.Tests.IO
sw.AddFile("sub/f1.txt", ms);
Assert.IsTrue(phy.FileExists("sub/f1.txt"));
Guid id;
string id;
scopedFileSystems = true; // pretend we have a scope
var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid());
var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId());
Assert.IsTrue(Directory.Exists(shadowfs + "/" + id));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
sw.AddFile("sub/f2.txt", ms);
@@ -551,10 +552,10 @@ namespace Umbraco.Tests.IO
sw.AddFile("sub/f1.txt", ms);
Assert.IsTrue(phy.FileExists("sub/f1.txt"));
Guid id;
string id;
scopedFileSystems = true; // pretend we have a scope
var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid());
var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId());
Assert.IsTrue(Directory.Exists(shadowfs + "/" + id));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
sw.AddFile("sub/f2.txt", ms);
+14 -8
View File
@@ -135,7 +135,7 @@ namespace Umbraco.Tests.Models
Assert.Throws<NotSupportedException>(() => prop.PublishValues());
// change
propertyType.IsPublishing = true;
propertyType.SupportsPublishing = true;
// can get value
// and now published value is null
@@ -364,6 +364,7 @@ namespace Umbraco.Tests.Models
[Test]
public void ContentPublishValuesWithMixedPropertyTypeVariations()
{
var propertyValidationService = new PropertyValidationService(Current.Factory.GetInstance<PropertyEditorCollection>(), Current.Factory.GetInstance<ServiceContext>().DataTypeService);
const string langFr = "fr-FR";
// content type varies by Culture
@@ -383,11 +384,15 @@ namespace Umbraco.Tests.Models
content.SetCultureName("hello", langFr);
Assert.IsFalse(content.PublishCulture(langFr)); // fails because prop1 is mandatory
Assert.IsTrue(content.PublishCulture(langFr)); // succeeds because names are ok (not validating properties here)
Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// fails because prop1 is mandatory
content.SetValue("prop1", "a", langFr);
Assert.IsFalse(content.PublishCulture(langFr)); // fails because prop2 is mandatory and invariant
Assert.IsTrue(content.PublishCulture(langFr)); // succeeds because names are ok (not validating properties here)
Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// fails because prop2 is mandatory and invariant
content.SetValue("prop2", "x");
Assert.IsTrue(content.PublishCulture(langFr)); // now it's ok
Assert.IsTrue(content.PublishCulture(langFr)); // still ok...
Assert.IsTrue(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// now it's ok
Assert.AreEqual("a", content.GetValue("prop1", langFr, published: true));
Assert.AreEqual("x", content.GetValue("prop2", published: true));
@@ -480,20 +485,21 @@ namespace Umbraco.Tests.Models
[Test]
public void ValidationTests()
{
var propertyType = new PropertyType("editor", ValueStorageType.Nvarchar) { Alias = "prop", IsPublishing = true };
var propertyType = new PropertyType("editor", ValueStorageType.Nvarchar) { Alias = "prop", SupportsPublishing = true };
var prop = new Property(propertyType);
prop.SetValue("a");
Assert.AreEqual("a", prop.GetValue());
Assert.IsNull(prop.GetValue(published: true));
var propertyValidationService = new PropertyValidationService(Current.Factory.GetInstance<PropertyEditorCollection>(), Current.Factory.GetInstance<ServiceContext>().DataTypeService);
Assert.IsTrue(prop.IsValid());
Assert.IsTrue(propertyValidationService.IsPropertyValid(prop));
propertyType.Mandatory = true;
Assert.IsTrue(prop.IsValid());
Assert.IsTrue(propertyValidationService.IsPropertyValid(prop));
prop.SetValue(null);
Assert.IsFalse(prop.IsValid());
Assert.IsFalse(propertyValidationService.IsPropertyValid(prop));
// can publish, even though invalid
prop.PublishValues();
@@ -15,6 +15,7 @@ using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Changes;
using Umbraco.Core.Strings;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing.Objects;
using Umbraco.Tests.Testing.Objects.Accessors;
@@ -180,8 +181,9 @@ namespace Umbraco.Tests.PublishedContent
dataSource,
globalSettings,
new SiteDomainHelper(),
contentTypeServiceBaseFactory,
Mock.Of<IEntityXmlSerializer>());
Mock.Of<IEntityXmlSerializer>(),
Mock.Of<IPublishedModelFactory>(),
new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }));
// invariant is the current default
_variationAccesor.VariationContext = new VariationContext();
@@ -40,7 +40,7 @@ namespace Umbraco.Tests.Routing
{
base.SetUp();
WebRuntimeComponent.CreateRoutes(
WebFinalComponent.CreateRoutes(
new TestUmbracoContextAccessor(),
TestObjects.GetGlobalSettings(),
new SurfaceControllerTypeCollection(Enumerable.Empty<Type>()),
@@ -81,7 +81,7 @@ namespace Umbraco.Tests.Runtimes
var composerTypes = typeLoader.GetTypes<IComposer>() // all of them
.Where(x => !x.FullName.StartsWith("Umbraco.Tests.")) // exclude test components
.Where(x => x != typeof(WebRuntimeComposer)); // exclude web runtime
.Where(x => x != typeof(WebInitialComposer)); // exclude web runtime
var composers = new Composers(composition, composerTypes, profilingLogger);
composers.Compose();
@@ -18,7 +18,7 @@ namespace Umbraco.Tests.Runtimes
new PluginViewEngine()
};
WebRuntimeComponent.WrapViewEngines(engines);
WebInitialComponent.WrapViewEngines(engines);
Assert.That(engines.Count, Is.EqualTo(2));
Assert.That(engines[0], Is.InstanceOf<ProfilingViewEngine>());
@@ -34,7 +34,7 @@ namespace Umbraco.Tests.Runtimes
new PluginViewEngine()
};
WebRuntimeComponent.WrapViewEngines(engines);
WebInitialComponent.WrapViewEngines(engines);
Assert.That(engines.Count, Is.EqualTo(2));
Assert.That(((ProfilingViewEngine)engines[0]).Inner, Is.InstanceOf<RenderViewEngine>());
@@ -50,7 +50,7 @@ namespace Umbraco.Tests.Runtimes
profiledEngine
};
WebRuntimeComponent.WrapViewEngines(engines);
WebInitialComponent.WrapViewEngines(engines);
Assert.That(engines[0], Is.SameAs(profiledEngine));
}
@@ -58,7 +58,7 @@ namespace Umbraco.Tests.Runtimes
[Test]
public void WrapViewEngines_CollectionIsNull_DoesNotThrow()
{
Assert.DoesNotThrow(() => WebRuntimeComponent.WrapViewEngines(null));
Assert.DoesNotThrow(() => WebInitialComponent.WrapViewEngines(null));
}
}
}
@@ -17,6 +17,7 @@ using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
@@ -97,8 +98,10 @@ namespace Umbraco.Tests.Scoping
documentRepository, mediaRepository, memberRepository,
DefaultCultureAccessor,
new DatabaseDataSource(),
Factory.GetInstance<IGlobalSettings>(), new SiteDomainHelper(), contentTypeServiceBaseFactory,
Factory.GetInstance<IEntityXmlSerializer>());
Factory.GetInstance<IGlobalSettings>(), new SiteDomainHelper(),
Factory.GetInstance<IEntityXmlSerializer>(),
Mock.Of<IPublishedModelFactory>(),
new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }));
}
protected UmbracoContext GetUmbracoContextNu(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable<IUrlProvider> urlProviders = null)
@@ -1,5 +1,6 @@
using System.Linq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories.Implement;
@@ -30,7 +31,7 @@ namespace Umbraco.Tests.Services
}
[Test]
public void SavingTest()
public void Saving_Culture()
{
var languageService = ServiceContext.LocalizationService;
@@ -80,13 +81,68 @@ namespace Umbraco.Tests.Services
ContentService.Saving += OnSaving;
ContentService.Saved += OnSaved;
contentService.Save(document);
ContentService.Saving -= OnSaving;
ContentService.Saved -= OnSaved;
try
{
contentService.Save(document);
}
finally
{
ContentService.Saving -= OnSaving;
ContentService.Saved -= OnSaved;
}
}
[Test]
public void PublishingTest()
public void Saving_Set_Value()
{
var contentTypeService = ServiceContext.ContentTypeService;
var contentType = MockedContentTypes.CreateTextPageContentType();
ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate);
contentTypeService.Save(contentType);
var contentService = ServiceContext.ContentService;
IContent document = new Content("content", -1, contentType);
void OnSaving(IContentService sender, ContentSavingEventArgs e)
{
var saved = e.SavedEntities.First();
Assert.IsTrue(document.GetValue<string>("title").IsNullOrWhiteSpace());
saved.SetValue("title", "title");
}
void OnSaved(IContentService sender, ContentSavedEventArgs e)
{
var saved = e.SavedEntities.First();
Assert.AreSame("title", document.GetValue<string>("title"));
//we're only dealing with invariant here
var propValue = saved.Properties["title"].Values.First(x => x.Culture == null && x.Segment == null);
Assert.AreEqual("title", propValue.EditedValue);
Assert.IsNull(propValue.PublishedValue);
}
ContentService.Saving += OnSaving;
ContentService.Saved += OnSaved;
try
{
contentService.Save(document);
}
finally
{
ContentService.Saving -= OnSaving;
ContentService.Saved -= OnSaved;
}
}
[Test]
public void Publishing_Culture()
{
var languageService = ServiceContext.LocalizationService;
@@ -136,9 +192,15 @@ namespace Umbraco.Tests.Services
ContentService.Publishing += OnPublishing;
ContentService.Published += OnPublished;
contentService.SaveAndPublish(document, "fr-FR");
ContentService.Publishing -= OnPublishing;
ContentService.Published -= OnPublished;
try
{
contentService.SaveAndPublish(document, "fr-FR");
}
finally
{
ContentService.Publishing -= OnPublishing;
ContentService.Published -= OnPublished;
}
document = contentService.GetById(document.Id);
@@ -148,7 +210,105 @@ namespace Umbraco.Tests.Services
}
[Test]
public void UnpublishingTest()
public void Publishing_Set_Value()
{
var contentTypeService = ServiceContext.ContentTypeService;
var contentType = MockedContentTypes.CreateTextPageContentType();
ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate);
contentTypeService.Save(contentType);
var contentService = ServiceContext.ContentService;
IContent document = new Content("content", -1, contentType);
void OnSaving(IContentService sender, ContentSavingEventArgs e)
{
var saved = e.SavedEntities.First();
Assert.IsTrue(document.GetValue<string>("title").IsNullOrWhiteSpace());
saved.SetValue("title", "title");
}
void OnSaved(IContentService sender, ContentSavedEventArgs e)
{
var saved = e.SavedEntities.First();
Assert.AreSame("title", document.GetValue<string>("title"));
//we're only dealing with invariant here
var propValue = saved.Properties["title"].Values.First(x => x.Culture == null && x.Segment == null);
Assert.AreEqual("title", propValue.EditedValue);
Assert.AreEqual("title", propValue.PublishedValue);
}
//We are binding to Saving (not Publishing), because the Publishing event is really just used for cancelling, it should not be
//used for setting values and it won't actually work! This is because the Publishing event is raised AFTER the values on the model
//are published, but Saving is raised BEFORE.
ContentService.Saving += OnSaving;
ContentService.Saved += OnSaved;
try
{
contentService.SaveAndPublish(document);
}
finally
{
ContentService.Saving -= OnSaving;
ContentService.Saved -= OnSaved;
}
}
[Test]
public void Publishing_Set_Mandatory_Value()
{
var contentTypeService = ServiceContext.ContentTypeService;
var contentType = MockedContentTypes.CreateTextPageContentType();
var titleProperty = contentType.PropertyTypes.First(x => x.Alias == "title");
titleProperty.Mandatory = true; // make this required!
ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate);
contentTypeService.Save(contentType);
var contentService = ServiceContext.ContentService;
IContent document = new Content("content", -1, contentType);
var result = contentService.SaveAndPublish(document);
Assert.IsFalse(result.Success);
Assert.AreEqual("title", result.InvalidProperties.First().Alias);
// when a service operation fails, the object is dirty and should not be re-used,
// re-create it
document = new Content("content", -1, contentType);
void OnSaving(IContentService sender, ContentSavingEventArgs e)
{
var saved = e.SavedEntities.First();
Assert.IsTrue(document.GetValue<string>("title").IsNullOrWhiteSpace());
saved.SetValue("title", "title");
}
//We are binding to Saving (not Publishing), because the Publishing event is really just used for cancelling, it should not be
//used for setting values and it won't actually work! This is because the Publishing event is raised AFTER the values on the model
//are published, but Saving is raised BEFORE.
ContentService.Saving += OnSaving;
try
{
result = contentService.SaveAndPublish(document);
Assert.IsTrue(result.Success); //will succeed now because we were able to specify the required value in the Saving event
}
finally
{
ContentService.Saving -= OnSaving;
}
}
[Test]
public void Unpublishing_Culture()
{
var languageService = ServiceContext.LocalizationService;
@@ -206,9 +366,15 @@ namespace Umbraco.Tests.Services
ContentService.Publishing += OnPublishing;
ContentService.Published += OnPublished;
contentService.CommitDocumentChanges(document);
ContentService.Publishing -= OnPublishing;
ContentService.Published -= OnPublished;
try
{
contentService.CommitDocumentChanges(document);
}
finally
{
ContentService.Publishing -= OnPublishing;
ContentService.Published -= OnPublished;
}
document = contentService.GetById(document.Id);
@@ -18,6 +18,7 @@ using Umbraco.Core.Services.Implement;
using Umbraco.Tests.Testing;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Cache;
using Umbraco.Core.PropertyEditors;
using Umbraco.Tests.LegacyXmlPublishedCache;
namespace Umbraco.Tests.Services
@@ -986,7 +987,10 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(parent.Published);
// content cannot publish values because they are invalid
Assert.IsNotEmpty(content.ValidateProperties());
var propertyValidationService = new PropertyValidationService(Factory.GetInstance<PropertyEditorCollection>(), ServiceContext.DataTypeService);
var isValid = propertyValidationService.IsPropertyDataValid(content, out var invalidProperties);
Assert.IsFalse(isValid);
Assert.IsNotEmpty(invalidProperties);
// and therefore cannot be published,
// because it did not have a published version at all
@@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
using Umbraco.Tests.Testing;
using Umbraco.Web;
@@ -70,8 +71,10 @@ namespace Umbraco.Tests.Services
documentRepository, mediaRepository, memberRepository,
DefaultCultureAccessor,
new DatabaseDataSource(),
Factory.GetInstance<IGlobalSettings>(), new SiteDomainHelper(), contentTypeServiceBaseFactory,
Factory.GetInstance<IEntityXmlSerializer>());
Factory.GetInstance<IGlobalSettings>(), new SiteDomainHelper(),
Factory.GetInstance<IEntityXmlSerializer>(),
Mock.Of<IPublishedModelFactory>(),
new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }));
}
public class LocalServerMessenger : ServerMessengerBase
@@ -1767,7 +1767,7 @@ h1, h2, h3, h4, h5, h6, p, blockquote {
h1, h2, h3, h4, h5, h6
{
font-family:'Open Sans', sans-serif;
font-family:'Lato', sans-serif;
}
h2, h3
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Composing;
@@ -202,6 +203,20 @@ namespace Umbraco.Tests.Strings
Assert.AreEqual(expected, result);
}
[TestCase("hello", "aGVsbG8")]
[TestCase("tad", "dGFk")]
[TestCase("AmqGr+Fd!~ééé", "QW1xR3IrRmQhfsOpw6nDqQ")]
public void UrlTokenEncoding(string value, string expected)
{
var bytes = Encoding.UTF8.GetBytes(value);
Console.WriteLine("base64: " + Convert.ToBase64String(bytes));
var encoded = StringExtensions.UrlTokenEncode(bytes);
Assert.AreEqual(expected, encoded);
var backBytes = StringExtensions.UrlTokenDecode(encoded);
var backString = Encoding.UTF8.GetString(backBytes);
Assert.AreEqual(value, backString);
}
// FORMAT STRINGS
@@ -116,7 +116,7 @@ namespace Umbraco.Tests.TestHelpers
var umbracoSettings = GetUmbracoSettings();
var globalSettings = GetGlobalSettings();
var urlProviders = Enumerable.Empty<IUrlProvider>();
var urlProviders = new UrlProviderCollection(Enumerable.Empty<IUrlProvider>());
if (accessor == null) accessor = new TestUmbracoContextAccessor();
+1 -1
View File
@@ -243,7 +243,7 @@ namespace Umbraco.Tests.Testing
Composition.WithCollectionBuilder<PropertyValueConverterCollectionBuilder>();
Composition.RegisterUnique<IPublishedContentTypeFactory, PublishedContentTypeFactory>();
Composition.RegisterUnique<IMediaPathScheme, OriginalMediaPathScheme>();
Composition.RegisterUnique<IMediaPathScheme, UniqueMediaPathScheme>();
// register empty content apps collection
Composition.WithCollectionBuilder<ContentAppFactoryCollectionBuilder>();
@@ -71,7 +71,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
TestObjects.GetUmbracoSettings(),
globalSettings,
Enumerable.Empty<IUrlProvider>(),
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -100,7 +100,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
TestObjects.GetUmbracoSettings(),
globalSettings,
Enumerable.Empty<IUrlProvider>(),
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -129,7 +129,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
TestObjects.GetUmbracoSettings(),
globalSettings,
Enumerable.Empty<IUrlProvider>(),
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -158,7 +158,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
TestObjects.GetUmbracoSettings(),
globalSettings,
Enumerable.Empty<IUrlProvider>(),
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -46,7 +46,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
TestObjects.GetUmbracoSettings(),
globalSettings,
Enumerable.Empty<IUrlProvider>(),
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -73,7 +73,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
TestObjects.GetUmbracoSettings(),
globalSettings,
Enumerable.Empty<IUrlProvider>(),
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -103,7 +103,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == Mock.Of<IWebRoutingSection>(routingSection => routingSection.UrlProviderMode == "Auto")),
globalSettings,
Enumerable.Empty<IUrlProvider>(),
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -140,7 +140,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == webRoutingSettings),
globalSettings,
Enumerable.Empty<IUrlProvider>(),
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -104,7 +104,7 @@ namespace Umbraco.Tests.Web
new TestDefaultCultureAccessor(),
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == Mock.Of<IWebRoutingSection>(routingSection => routingSection.UrlProviderMode == "Auto")),
globalSettings,
new[] { testUrlProvider.Object },
new UrlProviderCollection(new[] { testUrlProvider.Object }),
Mock.Of<IUserService>());
using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>()))
File diff suppressed because one or more lines are too long
@@ -1,19 +0,0 @@
LazyLoad.js([
'../lib/jquery/jquery.min.js',
'../lib/angular/angular.js',
'../lib/underscore/underscore-min.js',
'../lib/umbraco/Extensions.js',
'../js/app.js',
'../js/umbraco.resources.js',
'../js/umbraco.services.js',
'../js/umbraco.interceptors.js',
'../ServerVariables',
'../lib/signalr/jquery.signalR.js',
'../BackOffice/signalr/hubs',
'../js/umbraco.canvasdesigner.js'
], function () {
jQuery(document).ready(function () {
angular.bootstrap(document, ['Umbraco.canvasdesigner']);
});
});
@@ -3,8 +3,8 @@
function ContentEditController($rootScope, $scope, $routeParams, $q, $window,
appState, contentResource, entityResource, navigationService, notificationsService,
serverValidationManager, contentEditingHelper, treeService, formHelper, umbRequestHelper,
editorState, $http, eventsService, relationResource, overlayService, $location) {
serverValidationManager, contentEditingHelper, localizationService, formHelper, umbRequestHelper,
editorState, $http, eventsService, overlayService, $location) {
var evts = [];
var infiniteMode = $scope.infiniteModel && $scope.infiniteModel.infiniteMode;
@@ -410,6 +410,20 @@
}
}
/** Just shows a simple notification that there are client side validation issues to be fixed */
function showValidationNotification() {
//TODO: We need to make the validation UI much better, there's a lot of inconsistencies in v8 including colors, issues with the property groups and validation errors between variants
//need to show a notification else it's not clear there was an error.
localizationService.localizeMany([
"speechBubbles_validationFailedHeader",
"speechBubbles_validationFailedMessage"
]
).then(function (data) {
notificationsService.error(data[0], data[1]);
});
}
if ($scope.page.isNew) {
$scope.page.loading = true;
@@ -491,7 +505,7 @@
variants: $scope.content.variants, //set a model property for the dialog
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
submitButtonLabelKey: "buttons_saveToPublish",
submit: function (model) {
submit: function(model) {
model.submitButtonState = "busy";
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
@@ -499,28 +513,32 @@
saveMethod: contentResource.sendToPublish,
action: "sendToPublish",
showNotifications: false
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
}, function (err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
//don't reject, we've handled the error
return $q.when(err);
});
}).then(function(data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function(err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
//don't reject, we've handled the error
return $q.when(err);
});
},
close: function () {
close: function() {
overlayService.close();
}
};
overlayService.open(dialog);
}
else {
showValidationNotification();
}
}
else {
$scope.page.buttonGroupState = "busy";
@@ -547,7 +565,7 @@
variants: $scope.content.variants, //set a model property for the dialog
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
submitButtonLabelKey: "buttons_saveAndPublish",
submit: function (model) {
submit: function(model) {
model.submitButtonState = "busy";
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
@@ -555,14 +573,14 @@
saveMethod: contentResource.publish,
action: "publish",
showNotifications: false
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function (err) {
}).then(function(data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function(err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
@@ -571,13 +589,16 @@
return $q.when(err);
});
},
close: function () {
close: function() {
overlayService.close();
}
};
overlayService.open(dialog);
}
else {
showValidationNotification();
}
}
else {
//ensure the flags are set
@@ -608,7 +629,7 @@
variants: $scope.content.variants, //set a model property for the dialog
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
submitButtonLabelKey: "buttons_save",
submit: function (model) {
submit: function(model) {
model.submitButtonState = "busy";
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
@@ -616,28 +637,32 @@
saveMethod: $scope.saveMethod(),
action: "save",
showNotifications: false
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
}, function (err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
//don't reject, we've handled the error
return $q.when(err);
});
}).then(function(data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function(err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
//don't reject, we've handled the error
return $q.when(err);
});
},
close: function (oldModel) {
close: function(oldModel) {
overlayService.close();
}
};
overlayService.open(dialog);
}
else {
showValidationNotification();
}
}
else {
//ensure the flags are set
@@ -720,6 +745,9 @@
};
overlayService.open(dialog);
}
else {
showValidationNotification();
}
};
$scope.publishDescendants = function() {
@@ -776,50 +804,48 @@
};
overlayService.open(dialog);
}
else {
showValidationNotification();
}
};
$scope.preview = function (content) {
// Chromes popup blocker will kick in if a window is opened
// without the initial scoped request. This trick will fix that.
//
var previewWindow = $window.open('preview/?init=true', 'umbpreview');
// Build the correct path so both /#/ and #/ work.
var query = 'id=' + content.id;
if ($scope.culture) {
query += "#?culture=" + $scope.culture;
}
var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?' + query;
if (!$scope.busy) {
// Chromes popup blocker will kick in if a window is opened
// without the initial scoped request. This trick will fix that.
//
var previewWindow = $window.open('preview/?init=true', 'umbpreview');
// Build the correct path so both /#/ and #/ work.
var query = 'id=' + content.id;
//The user cannot save if they don't have access to do that, in which case we just want to preview
//and that's it otherwise they'll get an unauthorized access message
if (!_.contains(content.allowedActions, "A")) {
previewWindow.location.href = redirect;
}
else {
var selectedVariant = $scope.content.variants[0];
if ($scope.culture) {
query += "#?culture=" + $scope.culture;
}
var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?' + query;
//The user cannot save if they don't have access to do that, in which case we just want to preview
//and that's it otherwise they'll get an unauthorized access message
if (!_.contains(content.allowedActions, "A")) {
previewWindow.location.href = redirect;
}
else {
var selectedVariant = $scope.content.variants[0];
if ($scope.culture) {
var found = _.find($scope.content.variants, function (v) {
return (v.language && v.language.culture === $scope.culture);
});
if(found){
selectedVariant = found;
}
}
//ensure the save flag is set
selectedVariant.save = true;
performSave({ saveMethod: $scope.saveMethod(), action: "save" }).then(function (data) {
previewWindow.location.href = redirect;
}, function (err) {
//validation issues ....
var found = _.find($scope.content.variants, function (v) {
return (v.language && v.language.culture === $scope.culture);
});
if (found) {
selectedVariant = found;
}
}
//ensure the save flag is set
selectedVariant.save = true;
performSave({ saveMethod: $scope.saveMethod(), action: "save" }).then(function (data) {
previewWindow.location.href = redirect;
}, function (err) {
//validation issues ....
});
}
};
@@ -344,8 +344,8 @@
loadAuditTrail(true);
loadRedirectUrls();
setNodePublishStatus();
updateCurrentUrls();
}
updateCurrentUrls();
});
//ensure to unregister from all events!
@@ -46,7 +46,7 @@ Use this directive to generate a list of breadcrumbs.
(function () {
'use strict';
function BreadcrumbsDirective() {
function BreadcrumbsDirective($location, navigationService) {
function link(scope, el, attr, ctrl) {
@@ -58,6 +58,28 @@ Use this directive to generate a list of breadcrumbs.
}
};
scope.openPath = function (ancestor, event) {
// targeting a new tab/window?
if (event.ctrlKey ||
event.shiftKey ||
event.metaKey || // apple
(event.button && event.button === 1) // middle click, >IE9 + everyone else
) {
// yes, let the link open itself
return;
}
event.stopPropagation();
event.preventDefault();
var path = scope.pathTo(ancestor);
$location.path(path);
navigationService.clearSearch(["cculture"]);
}
scope.pathTo = function (ancestor) {
return "/" + scope.entityType + "/" + scope.entityType + "/edit/" + ancestor.id;
}
function onInit() {
if ("onOpen" in attr) {
scope.allowOnOpen = true;
@@ -34,8 +34,17 @@ Use this directive to generate color swatches to pick from.
}
scope.setColor = function (color, $index, $event) {
scope.selectedColor = color;
if (scope.onSelect) {
// did the value change?
if (scope.selectedColor != null && scope.selectedColor.value === color.value) {
// User clicked the currently selected color
// to remove the selection, they don't want
// to select any color after all.
// Unselect the color
color = null;
}
scope.selectedColor = color;
scope.onSelect({color: color, $index: $index, $event: $event});
$event.stopPropagation();
}
@@ -49,11 +49,11 @@ When this combination is hit an overview is opened with shortcuts based on the m
"name": "Design",
"shortcuts": [
{
"description": "Add tab",
"description": "Add group",
"keys": [
{"key": "alt"},
{"key": "shift"},
{"key": "t"}
{"key": "g"}
]
}
]
@@ -15,10 +15,12 @@ function umbFileUpload() {
el.on('change', function (event) {
var files = event.target.files;
//emit event upward
scope.$emit("filesSelected", { files: files });
scope.$emit("filesSelected", { files: files });
//clear the element value - this allows us to pick the same file again and again
el.val('');
});
}
};
}
angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload);
angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload);
@@ -3,23 +3,29 @@
/**
* Used to set the current client culture on all requests API requests
* @param {any} $q
* @param {any} $routeParams
*/
function cultureRequestInterceptor($q, $routeParams) {
function cultureRequestInterceptor($injector) {
return {
//dealing with requests:
'request': function (config) {
if (!Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) {
// no settings available, we're probably on the login screen
return config;
}
if (!config.url.match(RegExp(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "\/backoffice\/", "i"))) {
// it's not an API request, no handling
return config;
}
// it's an API request, add the current client culture as a header value
config.headers["X-UMB-CULTURE"] = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture;
var $routeParams = $injector.get("$routeParams");
if ($routeParams) {
// it's an API request, add the current client culture as a header value
config.headers["X-UMB-CULTURE"] = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture;
}
return config;
}
};

Some files were not shown because too many files have changed in this diff Show More