diff --git a/.gitignore b/.gitignore index bd7a0ffa9d..18239100ad 100644 --- a/.gitignore +++ b/.gitignore @@ -142,4 +142,6 @@ build/ui-docs.zip build/csharp-docs.zip build/msbuild.log .vs/ -src/packages/ \ No newline at end of file +src/packages/ +build/tools/ +src/PrecompiledWeb/* diff --git a/README.md b/README.md index 1d42543eb3..a9559c1850 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build status](https://ci.appveyor.com/api/projects/status/ikpgqxiw9v8opltv/branch/dev-v7?svg=true)](https://ci.appveyor.com/project/Umbraco/umbraco-cms-hs8dx/branch/dev-v7) + Umbraco CMS =========== The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 390,000 websites worldwide: [https://umbraco.com](https://umbraco.com) diff --git a/build/Build.bat b/build/Build.bat index d20eef70fb..f26c4877cd 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -113,18 +113,61 @@ REM This is necessary because SETLOCAL is on in InstallGit.cmd so that one might REM but the path setting is lost due to SETLOCAL SET PATH="C:\Program Files (x86)\Git\cmd";"C:\Program Files\Git\cmd";%PATH% +SET toolsFolder=%CD%\tools\ +IF NOT EXIST "%toolsFolder%" ( + MD tools +) + +SET nuGetExecutable=%CD%\tools\nuget.exe +IF NOT EXIST "%nuGetExecutable%" ( + ECHO Getting NuGet so we can fetch some tools + ECHO Downloading https://dist.nuget.org/win-x86-commandline/latest/nuget.exe to %nuGetExecutable% + powershell -Command "(New-Object Net.WebClient).DownloadFile('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', '%nuGetExecutable%')" +) + +:: We need 7za.exe for BuildBelle.bat +IF NOT EXIST "%toolsFolder%7za.exe" ( + ECHO 7zip not found - fetching now + "%nuGetExecutable%" install 7-Zip.CommandLine -OutputDirectory tools -Verbosity quiet +) + +:: We need vswhere.exe for VS2017+ +IF NOT EXIST "%toolsFolder%vswhere.exe" ( + ECHO vswhere not found - fetching now + "%nuGetExecutable%" install vswhere -OutputDirectory tools -Verbosity quiet +) + +:: Put 7za.exe and vswhere.exe in a predictable path (not version specific) +FOR /f "delims=" %%A in ('dir "%toolsFolder%7-Zip.CommandLine.*" /b') DO SET "sevenZipExePath=%toolsFolder%%%A\" +MOVE "%sevenZipExePath%tools\7za.exe" "%toolsFolder%7za.exe" + +FOR /f "delims=" %%A in ('dir "%toolsFolder%vswhere.*" /b') DO SET "vswhereExePath=%toolsFolder%%%A\" +MOVE "%vswhereExePath%tools\vswhere.exe" "%toolsFolder%vswhere.exe" + ECHO. ECHO Making sure we have a web.config IF NOT EXIST "%CD%\..\src\Umbraco.Web.UI\web.config" COPY "%CD%\..\src\Umbraco.Web.UI\web.Template.config" "%CD%\..\src\Umbraco.Web.UI\web.config" +for /f "usebackq tokens=1* delims=: " %%i in (`"%CD%\tools\vswhere.exe" -latest -requires Microsoft.Component.MSBuild`) do ( + if /i "%%i"=="installationPath" set InstallDir=%%j +) + +SET VSWherePath="%InstallDir%\MSBuild" + +ECHO. +ECHO Visual Studio is installed in: %InstallDir% + +SET MSBUILDPATH=C:\Program Files (x86)\MSBuild\14.0\Bin +SET MSBUILD="%MSBUILDPATH%\MsBuild.exe" + ECHO. ECHO Reporting NuGet version -"%CD%\..\src\.nuget\NuGet.exe" help | findstr "^NuGet Version:" +"%nuGetExecutable%" help | findstr "^NuGet Version:" ECHO. ECHO Restoring NuGet packages ECHO Into %nuGetFolder% -"%CD%\..\src\.nuget\NuGet.exe" restore "%CD%\..\src\umbraco.sln" -Verbosity Quiet -NonInteractive -PackagesDirectory "%nuGetFolder%" +"%nuGetExecutable%" restore "%CD%\..\src\umbraco.sln" -Verbosity Quiet -NonInteractive -PackagesDirectory "%nuGetFolder%" IF ERRORLEVEL 1 GOTO :error ECHO. @@ -134,7 +177,7 @@ ECHO This takes a few minutes and logging is set to report warnings ECHO and errors only so it might seems like nothing is happening for a while. ECHO You can check the msbuild.log file for progress. ECHO. -%MSBUILD% "Build.proj" /p:BUILD_RELEASE=%RELEASE% /p:BUILD_COMMENT=%COMMENT% /p:NugetPackagesDirectory="%nuGetFolder%" /consoleloggerparameters:Summary;ErrorsOnly /fileLogger +%MSBUILD% "Build.proj" /p:BUILD_RELEASE=%RELEASE% /p:BUILD_COMMENT=%COMMENT% /p:NugetPackagesDirectory="%nuGetFolder%" /p:VSWherePath=%VSWherePath% /consoleloggerparameters:Summary;ErrorsOnly /fileLogger IF ERRORLEVEL 1 GOTO error ECHO. diff --git a/build/BuildBelle.bat b/build/BuildBelle.bat index 8a07ee380a..78b7736c2b 100644 --- a/build/BuildBelle.bat +++ b/build/BuildBelle.bat @@ -1,33 +1,59 @@ @ECHO OFF SETLOCAL + :: SETLOCAL is on, so changes to the path not persist to the actual user's path -SET release=%1 -ECHO Installing Npm NuGet Package - -SET nuGetFolder=%CD%\..\src\packages\ -ECHO Configured packages folder: %nuGetFolder% +SET toolsFolder=%CD%\tools\ ECHO Current folder: %CD% -%CD%\..\src\.nuget\NuGet.exe install Npm.js -OutputDirectory %nuGetFolder% -Verbosity quiet +SET nodeFileName=node-v6.9.1-win-x86.7z +SET nodeExtractFolder=%toolsFolder%node.js.691 -for /f "delims=" %%A in ('dir %nuGetFolder%node.js.* /b') do set "nodePath=%nuGetFolder%%%A\" -for /f "delims=" %%A in ('dir %nuGetFolder%npm.js.* /b') do set "npmPath=%nuGetFolder%%%A\tools\" +IF NOT EXIST "%nodeExtractFolder%" ( + ECHO Downloading http://nodejs.org/dist/v6.9.1/%nodeFileName% to %toolsFolder%%nodeFileName% + powershell -Command "(New-Object Net.WebClient).DownloadFile('http://nodejs.org/dist/v6.9.1/%nodeFileName%', '%toolsFolder%%nodeFileName%')" + ECHO Extracting %nodeFileName% to %nodeExtractFolder% + "%toolsFolder%\7za.exe" x "%toolsFolder%\%nodeFileName%" -o"%nodeExtractFolder%" -aos > nul +) +FOR /f "delims=" %%A in ('dir "%nodeExtractFolder%\node*" /b') DO SET "nodePath=%nodeExtractFolder%\%%A" -ECHO Adding Npm and Node to path -REM SETLOCAL is on, so changes to the path not persist to the actual user's path -PATH=%npmPath%;%nodePath%;%PATH% -SET buildFolder=%CD% +SET nuGetExecutable=%CD%\tools\nuget.exe +IF NOT EXIST "%nuGetExecutable%" ( + ECHO Downloading https://dist.nuget.org/win-x86-commandline/latest/nuget.exe to %nuGetExecutable% + powershell -Command "(New-Object Net.WebClient).DownloadFile('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', '%nuGetExecutable%')" +) -ECHO Change directory to %CD%\..\src\Umbraco.Web.UI.Client\ -CD %CD%\..\src\Umbraco.Web.UI.Client\ +SET drive=%CD:~0,2% +SET nuGetFolder=%drive%\packages\ +FOR /f "delims=" %%A in ('dir "%nuGetFolder%npm.*" /b') DO SET "npmPath=%nuGetFolder%%%A\" -ECHO Do npm install and the grunt build of Belle -call npm cache clean --quiet -call npm install --quiet -call npm install -g grunt-cli --quiet -call npm install -g bower --quiet -call grunt build --buildversion=%release% +IF [%npmPath%] == [] GOTO :installnpm +IF NOT [%npmPath%] == [] GOTO :build -ECHO Move back to the build folder -CD %buildFolder% \ No newline at end of file +:installnpm + ECHO Downloading npm + ECHO Configured packages folder: %nuGetFolder% + ECHO Installing Npm NuGet Package + "%nuGetExecutable%" install Npm -OutputDirectory %nuGetFolder% -Verbosity detailed + REM Ensures that we look for the just downloaded NPM, not whatever the user has installed on their machine + FOR /f "delims=" %%A in ('dir %nuGetFolder%npm.* /b') DO SET "npmPath=%nuGetFolder%%%A\" + GOTO :build + +:build + ECHO Adding Npm and Node to path + REM SETLOCAL is on, so changes to the path not persist to the actual user's path + PATH="%npmPath%";"%nodePath%";%PATH% + SET buildFolder=%CD% + + ECHO Change directory to %CD%\..\src\Umbraco.Web.UI.Client\ + CD %CD%\..\src\Umbraco.Web.UI.Client\ + + ECHO Do npm install and the grunt build of Belle + call npm cache clean --quiet + call npm install --quiet + call npm install -g grunt-cli --quiet + call npm install -g bower --quiet + call grunt build --buildversion=%release% + + ECHO Move back to the build folder + CD "%buildFolder%" \ No newline at end of file diff --git a/build/BuildDocs.ps1 b/build/BuildDocs.ps1 index dcb3a85cc1..7b13f98ca4 100644 --- a/build/BuildDocs.ps1 +++ b/build/BuildDocs.ps1 @@ -57,6 +57,20 @@ $IndexPath = "../src/umbraco.web.ui.client/docs/api/index.html" # Build the solution in debug mode $SolutionPath = Join-Path -Path $SolutionRoot -ChildPath "umbraco.sln" + +# Go get nuget.exe if we don't hae it +$NuGet = "$ToolsRoot\nuget.exe" +$FileExists = Test-Path $NuGet +If ($FileExists -eq $False) { + Write-Host "Retrieving nuget.exe..." + $SourceNugetExe = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" + Invoke-WebRequest $SourceNugetExe -OutFile $NuGet +} + +#restore nuget packages +Write-Host "Restoring nuget packages..." +& $NuGet restore $SolutionPath + & $MSBuild "$SolutionPath" /p:Configuration=Debug /maxcpucount /t:Clean if (-not $?) { diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index acda20f82a..c4aaf7344e 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -1,6 +1,6 @@ - + UmbracoCms.Core 7.0.0 Umbraco Cms Core Binaries @@ -37,8 +37,8 @@ - - + + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index d30ae4efa3..8d58ec43a3 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -1,6 +1,6 @@ - + UmbracoCms 7.0.0 Umbraco Cms @@ -19,7 +19,7 @@ - + diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 35c4c4846d..e482eb5d6e 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -332,6 +332,7 @@ + @@ -389,6 +390,10 @@ + + + + diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 5ed0e7d05b..e591b5dfcb 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,2 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) -7.6.0 -RC3 \ No newline at end of file +7.6.3 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 6628e7b793..d7e4c9a204 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.6.0")] -[assembly: AssemblyInformationalVersion("7.6.0-RC3")] \ No newline at end of file +[assembly: AssemblyFileVersion("7.6.3")] +[assembly: AssemblyInformationalVersion("7.6.3")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index 51a39e15df..2bab497b2b 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -139,6 +139,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings internal CommaDelimitedConfigurationElement DisallowedUploadFiles { get { return GetOptionalDelimitedElement("disallowedUploadFiles", new[] {"ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd"}); } + } + + [ConfigurationProperty("allowedUploadFiles")] + internal CommaDelimitedConfigurationElement AllowedUploadFiles + { + get { return GetOptionalDelimitedElement("allowedUploadFiles", new string[0]); } } [ConfigurationProperty("cloneXmlContent")] @@ -307,6 +313,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings IEnumerable IContentSection.DisallowedUploadFiles { get { return DisallowedUploadFiles; } + } + + IEnumerable IContentSection.AllowedUploadFiles + { + get { return AllowedUploadFiles; } } bool IContentSection.CloneXmlContent diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs new file mode 100644 index 0000000000..a4f182b373 --- /dev/null +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs @@ -0,0 +1,19 @@ +using System.Linq; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public static class ContentSectionExtensions + { + /// + /// Determines if file extension is allowed for upload based on (optional) white list and black list + /// held in settings. + /// Allow upload if extension is whitelisted OR if there is no whitelist and extension is NOT blacklisted. + /// + public static bool IsFileAllowedForUpload(this IContentSection contentSection, string extension) + { + return contentSection.AllowedUploadFiles.Any(x => x.InvariantEquals(extension)) || + (contentSection.AllowedUploadFiles.Any() == false && + contentSection.DisallowedUploadFiles.Any(x => x.InvariantEquals(extension)) == false); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index d73b3b9e41..7e874c9582 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -52,7 +52,9 @@ namespace Umbraco.Core.Configuration.UmbracoSettings MacroErrorBehaviour MacroErrorBehaviour { get; } - IEnumerable DisallowedUploadFiles { get; } + IEnumerable DisallowedUploadFiles { get; } + + IEnumerable AllowedUploadFiles { get; } bool CloneXmlContent { get; } diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index f03a43fd29..28952540b2 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.6.0"); + private static readonly Version Version = new Version("7.6.3"); /// /// Gets the current version of Umbraco. @@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration /// Gets the version comment (like beta or RC). /// /// The version comment. - public static string CurrentComment { get { return "RC3"; } } + public static string CurrentComment { get { return ""; } } // Get the version of the umbraco.dll by looking at a class in that dll // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs index 7815747f18..63562eb53e 100644 --- a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs @@ -1,85 +1,177 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Security.Permissions; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Events { - /// - /// Event args for a strongly typed object that can support cancellation - /// - /// - [HostProtection(SecurityAction.LinkDemand, SharedState = true)] - public class CancellableObjectEventArgs : CancellableEventArgs, IEquatable> - { - public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) + /// + /// Used as a base class for the generic type CancellableObjectEventArgs{T} so that we can get direct 'object' access to the underlying EventObject + /// + [HostProtection(SecurityAction.LinkDemand, SharedState = true)] + public abstract class CancellableObjectEventArgs : CancellableEventArgs + { + protected CancellableObjectEventArgs(object eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) : base(canCancel, messages, additionalData) - { + { EventObject = eventObject; } - public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages eventMessages) + protected CancellableObjectEventArgs(object eventObject, bool canCancel, EventMessages eventMessages) : base(canCancel, eventMessages) { EventObject = eventObject; } - public CancellableObjectEventArgs(T eventObject, EventMessages eventMessages) + protected CancellableObjectEventArgs(object eventObject, EventMessages eventMessages) : this(eventObject, true, eventMessages) { } + protected CancellableObjectEventArgs(object eventObject, bool canCancel) + : base(canCancel) + { + EventObject = eventObject; + } + + protected CancellableObjectEventArgs(object eventObject) + : this(eventObject, true) + { + } + + /// + /// Returns the object relating to the event + /// + /// + /// This is protected so that inheritors can expose it with their own name + /// + internal object EventObject { get; set; } + + } + + /// + /// Event args for a strongly typed object that can support cancellation + /// + /// + [HostProtection(SecurityAction.LinkDemand, SharedState = true)] + public class CancellableObjectEventArgs : CancellableObjectEventArgs, IEquatable> + { + public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) + : base(eventObject, canCancel, messages, additionalData) + { + } + + public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + } + + public CancellableObjectEventArgs(T eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + } + public CancellableObjectEventArgs(T eventObject, bool canCancel) - : base(canCancel) - { - EventObject = eventObject; - } + : base(eventObject, canCancel) + { + } - public CancellableObjectEventArgs(T eventObject) - : this(eventObject, true) - { - } + public CancellableObjectEventArgs(T eventObject) + : base(eventObject) + { + } - /// - /// Returns the object relating to the event - /// - /// - /// This is protected so that inheritors can expose it with their own name - /// - protected T EventObject { get; set; } + /// + /// Returns the object relating to the event + /// + /// + /// This is protected so that inheritors can expose it with their own name + /// + protected new T EventObject + { + get { return (T) base.EventObject; } + set { base.EventObject = value; } + } - public bool Equals(CancellableObjectEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && EqualityComparer.Default.Equals(EventObject, other.EventObject); - } + public bool Equals(CancellableObjectEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && EqualityComparer.Default.Equals(EventObject, other.EventObject); + } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((CancellableObjectEventArgs) obj); - } + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CancellableObjectEventArgs)obj); + } - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(EventObject); - } - } + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(EventObject); + } + } - public static bool operator ==(CancellableObjectEventArgs left, CancellableObjectEventArgs right) - { - return Equals(left, right); - } + public static bool operator ==(CancellableObjectEventArgs left, CancellableObjectEventArgs right) + { + return Equals(left, right); + } - public static bool operator !=(CancellableObjectEventArgs left, CancellableObjectEventArgs right) - { - return !Equals(left, right); - } - } + public static bool operator !=(CancellableObjectEventArgs left, CancellableObjectEventArgs right) + { + return !Equals(left, right); + } + } + + [HostProtection(SecurityAction.LinkDemand, SharedState = true)] + public class CancellableEnumerableObjectEventArgs : CancellableObjectEventArgs>, IEquatable> + { + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) + : base(eventObject, canCancel, messages, additionalData) + { } + + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { } + + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { } + + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel) + : base(eventObject, canCancel) + { } + + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject) + : base(eventObject) + { } + + public bool Equals(CancellableEnumerableObjectEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return EventObject.SequenceEqual(other.EventObject); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CancellableEnumerableObjectEventArgs)obj); + } + + public override int GetHashCode() + { + return HashCodeHelper.GetHashCode(EventObject); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs index df13363b95..d0a4f024e1 100644 --- a/src/Umbraco.Core/Events/DeleteEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteEventArgs.cs @@ -1,9 +1,14 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Umbraco.Core.Events { - public class DeleteEventArgs : CancellableObjectEventArgs>, IEquatable>, IDeletingMediaFilesEventArgs + [SupersedeEvent(typeof(SaveEventArgs<>))] + [SupersedeEvent(typeof(PublishEventArgs<>))] + [SupersedeEvent(typeof(MoveEventArgs<>))] + [SupersedeEvent(typeof(CopyEventArgs<>))] + public class DeleteEventArgs : CancellableEnumerableObjectEventArgs, IEquatable>, IDeletingMediaFilesEventArgs { /// /// Constructor accepting multiple entities that are used in the delete operation @@ -106,7 +111,7 @@ namespace Umbraco.Core.Events { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && MediaFilesToDelete.Equals(other.MediaFilesToDelete); + return base.Equals(other) && MediaFilesToDelete.SequenceEqual(other.MediaFilesToDelete); } public override bool Equals(object obj) diff --git a/src/Umbraco.Core/Events/ImportEventArgs.cs b/src/Umbraco.Core/Events/ImportEventArgs.cs index dcecf5c36b..892149c0a2 100644 --- a/src/Umbraco.Core/Events/ImportEventArgs.cs +++ b/src/Umbraco.Core/Events/ImportEventArgs.cs @@ -4,7 +4,7 @@ using System.Xml.Linq; namespace Umbraco.Core.Events { - public class ImportEventArgs : CancellableObjectEventArgs>, IEquatable> + public class ImportEventArgs : CancellableEnumerableObjectEventArgs, IEquatable> { /// /// Constructor accepting an XElement with the xml being imported diff --git a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs index 231e58c07e..4477faea50 100644 --- a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs +++ b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Packaging.Models; namespace Umbraco.Core.Events { - internal class ImportPackageEventArgs : CancellableObjectEventArgs>, IEquatable> + internal class ImportPackageEventArgs : CancellableEnumerableObjectEventArgs, IEquatable> { private readonly MetaData _packageMetaData; @@ -32,7 +32,8 @@ namespace Umbraco.Core.Events public bool Equals(ImportPackageEventArgs other) { if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; + if (ReferenceEquals(this, other)) return true; + //TODO: MetaData for package metadata has no equality operators :/ return base.Equals(other) && _packageMetaData.Equals(other._packageMetaData); } diff --git a/src/Umbraco.Core/Events/PublishEventArgs.cs b/src/Umbraco.Core/Events/PublishEventArgs.cs index 1aa7c2308c..10bf94146c 100644 --- a/src/Umbraco.Core/Events/PublishEventArgs.cs +++ b/src/Umbraco.Core/Events/PublishEventArgs.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Umbraco.Core.Events { - public class PublishEventArgs : CancellableObjectEventArgs>, IEquatable> + public class PublishEventArgs : CancellableEnumerableObjectEventArgs, IEquatable> { /// /// Constructor accepting multiple entities that are used in the publish operation diff --git a/src/Umbraco.Core/Events/SaveEventArgs.cs b/src/Umbraco.Core/Events/SaveEventArgs.cs index e816a8f8bd..cd19038d8e 100644 --- a/src/Umbraco.Core/Events/SaveEventArgs.cs +++ b/src/Umbraco.Core/Events/SaveEventArgs.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; +using System.Linq; namespace Umbraco.Core.Events { - public class SaveEventArgs : CancellableObjectEventArgs> + public class SaveEventArgs : CancellableEnumerableObjectEventArgs { /// /// Constructor accepting multiple entities that are used in the saving operation @@ -116,7 +117,5 @@ namespace Umbraco.Core.Events { get { return EventObject; } } - - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs b/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs index 4dcb15515a..c703a10cb4 100644 --- a/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs +++ b/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs @@ -1,6 +1,8 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Events { @@ -15,6 +17,7 @@ namespace Umbraco.Core.Events /// public abstract class ScopeEventDispatcherBase : IEventDispatcher { + //events will be enlisted in the order they are raised private List _events; private readonly bool _raiseCancelable; @@ -73,29 +76,263 @@ namespace Umbraco.Core.Events { if (_events == null) return Enumerable.Empty(); - + switch (filter) { case EventDefinitionFilter.All: - return _events; + return FilterSupersededAndUpdateToLatestEntity(_events); case EventDefinitionFilter.FirstIn: var l1 = new OrderedHashSet(); foreach (var e in _events) { l1.Add(e); } - return l1; + return FilterSupersededAndUpdateToLatestEntity(l1); case EventDefinitionFilter.LastIn: var l2 = new OrderedHashSet(keepOldest: false); foreach (var e in _events) { l2.Add(e); } - return l2; + return FilterSupersededAndUpdateToLatestEntity(l2); default: throw new ArgumentOutOfRangeException("filter", filter, null); } } + + private class EventDefinitionTypeData + { + public IEventDefinition EventDefinition { get; set; } + public Type EventArgType { get; set; } + public SupersedeEventAttribute[] SupersedeAttributes { get; set; } + } + + /// + /// This will iterate over the events (latest first) and filter out any events or entities in event args that are included + /// in more recent events that Supersede previous ones. For example, If an Entity has been Saved and then Deleted, we don't want + /// to raise the Saved event (well actually we just don't want to include it in the args for that saved event) + /// + /// + /// + private static IEnumerable FilterSupersededAndUpdateToLatestEntity(IReadOnlyList events) + { + //used to keep the 'latest' entity and associated event definition data + var allEntities = new List>(); + + //tracks all CancellableObjectEventArgs instances in the events which is the only type of args we can work with + var cancelableArgs = new List(); + + var result = new List(); + + //This will eagerly load all of the event arg types and their attributes so we don't have to continuously look this data up + var allArgTypesWithAttributes = events.Select(x => x.Args.GetType()) + .Distinct() + .ToDictionary(x => x, x => x.GetCustomAttributes(false).ToArray()); + + //Iterate all events and collect the actual entities in them and relates them to their corresponding EventDefinitionTypeData + //we'll process the list in reverse because events are added in the order they are raised and we want to filter out + //any entities from event args that are not longer relevant + //(i.e. if an item is Deleted after it's Saved, we won't include the item in the Saved args) + for (var index = events.Count - 1; index >= 0; index--) + { + var eventDefinition = events[index]; + + var argType = eventDefinition.Args.GetType(); + var attributes = allArgTypesWithAttributes[eventDefinition.Args.GetType()]; + + var meta = new EventDefinitionTypeData + { + EventDefinition = eventDefinition, + EventArgType = argType, + SupersedeAttributes = attributes + }; + + var args = eventDefinition.Args as CancellableObjectEventArgs; + if (args != null) + { + var list = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject); + + if (list == null) + { + //extract the event object + var obj = args.EventObject as IEntity; + if (obj != null) + { + //Now check if this entity already exists in other event args that supersede this current event arg type + if (IsFiltered(obj, meta, allEntities) == false) + { + //if it's not filtered we can adde these args to the response + cancelableArgs.Add(args); + result.Add(eventDefinition); + //track the entity + allEntities.Add(Tuple.Create(obj, meta)); + } + } + else + { + //Can't retrieve the entity so cant' filter or inspect, just add to the output + result.Add(eventDefinition); + } + } + else + { + var toRemove = new List(); + foreach (var entity in list) + { + //extract the event object + var obj = entity as IEntity; + if (obj != null) + { + //Now check if this entity already exists in other event args that supersede this current event arg type + if (IsFiltered(obj, meta, allEntities)) + { + //track it to be removed + toRemove.Add(obj); + } + else + { + //track the entity, it's not filtered + allEntities.Add(Tuple.Create(obj, meta)); + } + } + else + { + //we don't need to do anything here, we can't cast to IEntity so we cannot filter, so it will just remain in the list + } + } + + //remove anything that has been filtered + foreach (var entity in toRemove) + { + list.Remove(entity); + } + + //track the event and include in the response if there's still entities remaining in the list + if (list.Count > 0) + { + if (toRemove.Count > 0) + { + //re-assign if the items have changed + args.EventObject = list; + } + cancelableArgs.Add(args); + result.Add(eventDefinition); + } + } + } + else + { + //it's not a cancelable event arg so we just include it in the result + result.Add(eventDefinition); + } + } + + //Now we'll deal with ensuring that only the latest(non stale) entities are used throughout all event args + UpdateToLatestEntities(allEntities, cancelableArgs); + + //we need to reverse the result since we've been adding by latest added events first! + result.Reverse(); + + return result; + } + + private static void UpdateToLatestEntities(IEnumerable> allEntities, IEnumerable cancelableArgs) + { + //Now we'll deal with ensuring that only the latest(non stale) entities are used throughout all event args + + var latestEntities = new OrderedHashSet(keepOldest: true); + foreach (var entity in allEntities.OrderByDescending(entity => entity.Item1.UpdateDate)) + { + latestEntities.Add(entity.Item1); + } + + foreach (var args in cancelableArgs) + { + var list = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject); + if (list == null) + { + //try to find the args entity in the latest entity - based on the equality operators, this will + //match by Id since that is the default equality checker for IEntity. If one is found, than it is + //the most recent entity instance so update the args with that instance so we don't emit a stale instance. + var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, args.EventObject)); + if (foundEntity != null) + { + args.EventObject = foundEntity; + } + } + else + { + var updated = false; + + for (int i = 0; i < list.Count; i++) + { + //try to find the args entity in the latest entity - based on the equality operators, this will + //match by Id since that is the default equality checker for IEntity. If one is found, than it is + //the most recent entity instance so update the args with that instance so we don't emit a stale instance. + var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, list[i])); + if (foundEntity != null) + { + list[i] = foundEntity; + updated = true; + } + } + + if (updated) + { + args.EventObject = list; + } + } + } + } + + /// + /// This will check against all of the processed entity/events (allEntities) to see if this entity already exists in + /// event args that supersede the event args being passed in and if so returns true. + /// + /// + /// + /// + /// + private static bool IsFiltered( + IEntity entity, + EventDefinitionTypeData eventDef, + List> allEntities) + { + var argType = eventDef.EventDefinition.Args.GetType(); + + //check if the entity is found in any processed event data that could possible supersede this one + var foundByEntity = allEntities + .Where(x => x.Item2.SupersedeAttributes.Length > 0 + //if it's the same arg type than it cannot supersede + && x.Item2.EventArgType != argType + && Equals(x.Item1, entity)) + .ToArray(); + + //no args have been processed with this entity so it should not be filtered + if (foundByEntity.Length == 0) + return false; + + if (argType.IsGenericType) + { + var supercededBy = foundByEntity + .FirstOrDefault(x => + x.Item2.SupersedeAttributes.Any(y => + //if the attribute type is a generic type def then compare with the generic type def of the event arg + (y.SupersededEventArgsType.IsGenericTypeDefinition && y.SupersededEventArgsType == argType.GetGenericTypeDefinition()) + //if the attribute type is not a generic type def then compare with the normal type of the event arg + || (y.SupersededEventArgsType.IsGenericTypeDefinition == false && y.SupersededEventArgsType == argType))); + return supercededBy != null; + } + else + { + var supercededBy = foundByEntity + .FirstOrDefault(x => + x.Item2.SupersedeAttributes.Any(y => + //since the event arg type is not a generic type, then we just compare type 1:1 + y.SupersededEventArgsType == argType)); + return supercededBy != null; + } + } public void ScopeExit(bool completed) { diff --git a/src/Umbraco.Core/Events/SupersedeEventAttribute.cs b/src/Umbraco.Core/Events/SupersedeEventAttribute.cs new file mode 100644 index 0000000000..c7a14ea158 --- /dev/null +++ b/src/Umbraco.Core/Events/SupersedeEventAttribute.cs @@ -0,0 +1,20 @@ +using System; + +namespace Umbraco.Core.Events +{ + /// + /// This is used to know if the event arg attributed should supersede another event arg type when + /// tracking events for the same entity. If one event args supercedes another then the event args that have been superseded + /// will mean that the event will not be dispatched or the args will be filtered to exclude the entity. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + internal class SupersedeEventAttribute : Attribute + { + public Type SupersededEventArgsType { get; private set; } + + public SupersedeEventAttribute(Type supersededEventArgsType) + { + SupersededEventArgsType = supersededEventArgsType; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/HashCodeCombiner.cs b/src/Umbraco.Core/HashCodeCombiner.cs index b97a3cfacf..d3a55d5256 100644 --- a/src/Umbraco.Core/HashCodeCombiner.cs +++ b/src/Umbraco.Core/HashCodeCombiner.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -7,32 +6,32 @@ using System.Text; namespace Umbraco.Core { - /// - /// Used to create a hash code from multiple objects. - /// - /// - /// .Net has a class the same as this: System.Web.Util.HashCodeCombiner and of course it works for all sorts of things - /// which we've not included here as we just need a quick easy class for this in order to create a unique - /// hash of directories/files to see if they have changed. - /// - internal class HashCodeCombiner - { - private long _combinedHash = 5381L; + /// + /// Used to create a hash code from multiple objects. + /// + /// + /// .Net has a class the same as this: System.Web.Util.HashCodeCombiner and of course it works for all sorts of things + /// which we've not included here as we just need a quick easy class for this in order to create a unique + /// hash of directories/files to see if they have changed. + /// + internal class HashCodeCombiner + { + private long _combinedHash = 5381L; - internal void AddInt(int i) - { - _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i; - } + internal void AddInt(int i) + { + _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i; + } - internal void AddObject(object o) - { - AddInt(o.GetHashCode()); - } + internal void AddObject(object o) + { + AddInt(o.GetHashCode()); + } - internal void AddDateTime(DateTime d) - { - AddInt(d.GetHashCode()); - } + internal void AddDateTime(DateTime d) + { + AddInt(d.GetHashCode()); + } internal void AddString(string s) { @@ -40,61 +39,61 @@ namespace Umbraco.Core AddInt((StringComparer.InvariantCulture).GetHashCode(s)); } - internal void AddCaseInsensitiveString(string s) - { - if (s != null) - AddInt((StringComparer.InvariantCultureIgnoreCase).GetHashCode(s)); - } + internal void AddCaseInsensitiveString(string s) + { + if (s != null) + AddInt((StringComparer.InvariantCultureIgnoreCase).GetHashCode(s)); + } - internal void AddFileSystemItem(FileSystemInfo f) - { - //if it doesn't exist, don't proceed. - if (!f.Exists) - return; + internal void AddFileSystemItem(FileSystemInfo f) + { + //if it doesn't exist, don't proceed. + if (!f.Exists) + return; - AddCaseInsensitiveString(f.FullName); - AddDateTime(f.CreationTimeUtc); - AddDateTime(f.LastWriteTimeUtc); - - //check if it is a file or folder - var fileInfo = f as FileInfo; - if (fileInfo != null) - { - AddInt(fileInfo.Length.GetHashCode()); - } - - var dirInfo = f as DirectoryInfo; - if (dirInfo != null) - { - foreach (var d in dirInfo.GetFiles()) - { - AddFile(d); - } - foreach (var s in dirInfo.GetDirectories()) - { - AddFolder(s); - } - } - } + AddCaseInsensitiveString(f.FullName); + AddDateTime(f.CreationTimeUtc); + AddDateTime(f.LastWriteTimeUtc); - internal void AddFile(FileInfo f) - { - AddFileSystemItem(f); - } + //check if it is a file or folder + var fileInfo = f as FileInfo; + if (fileInfo != null) + { + AddInt(fileInfo.Length.GetHashCode()); + } - internal void AddFolder(DirectoryInfo d) - { - AddFileSystemItem(d); - } + var dirInfo = f as DirectoryInfo; + if (dirInfo != null) + { + foreach (var d in dirInfo.GetFiles()) + { + AddFile(d); + } + foreach (var s in dirInfo.GetDirectories()) + { + AddFolder(s); + } + } + } - /// - /// Returns the hex code of the combined hash code - /// - /// - internal string GetCombinedHashCode() - { - return _combinedHash.ToString("x", CultureInfo.InvariantCulture); - } + internal void AddFile(FileInfo f) + { + AddFileSystemItem(f); + } - } + internal void AddFolder(DirectoryInfo d) + { + AddFileSystemItem(d); + } + + /// + /// Returns the hex code of the combined hash code + /// + /// + internal string GetCombinedHashCode() + { + return _combinedHash.ToString("x", CultureInfo.InvariantCulture); + } + + } } diff --git a/src/Umbraco.Core/HashCodeHelper.cs b/src/Umbraco.Core/HashCodeHelper.cs new file mode 100644 index 0000000000..f0f281056d --- /dev/null +++ b/src/Umbraco.Core/HashCodeHelper.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; + +namespace Umbraco.Core +{ + /// + /// Borrowed from http://stackoverflow.com/a/2575444/694494 + /// + internal static class HashCodeHelper + { + public static int GetHashCode(T1 arg1, T2 arg2) + { + unchecked + { + return 31 * arg1.GetHashCode() + arg2.GetHashCode(); + } + } + + public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3) + { + unchecked + { + int hash = arg1.GetHashCode(); + hash = 31 * hash + arg2.GetHashCode(); + return 31 * hash + arg3.GetHashCode(); + } + } + + public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3, + T4 arg4) + { + unchecked + { + int hash = arg1.GetHashCode(); + hash = 31 * hash + arg2.GetHashCode(); + hash = 31 * hash + arg3.GetHashCode(); + return 31 * hash + arg4.GetHashCode(); + } + } + + public static int GetHashCode(T[] list) + { + unchecked + { + int hash = 0; + foreach (var item in list) + { + if (item == null) continue; + hash = 31 * hash + item.GetHashCode(); + } + return hash; + } + } + + public static int GetHashCode(IEnumerable list) + { + unchecked + { + int hash = 0; + foreach (var item in list) + { + if (item == null) continue; + hash = 31 * hash + item.GetHashCode(); + } + return hash; + } + } + + /// + /// Gets a hashcode for a collection for that the order of items + /// does not matter. + /// So {1, 2, 3} and {3, 2, 1} will get same hash code. + /// + public static int GetHashCodeForOrderNoMatterCollection( + IEnumerable list) + { + unchecked + { + int hash = 0; + int count = 0; + foreach (var item in list) + { + if (item == null) continue; + hash += item.GetHashCode(); + count++; + } + return 31 * hash + count.GetHashCode(); + } + } + + /// + /// Alternative way to get a hashcode is to use a fluent + /// interface like this:
+ /// return 0.CombineHashCode(field1).CombineHashCode(field2). + /// CombineHashCode(field3); + ///
+ public static int CombineHashCode(this int hashCode, T arg) + { + unchecked + { + return 31 * hashCode + arg.GetHashCode(); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index c57cf94cef..fc6490e8cd 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -159,7 +159,7 @@ namespace Umbraco.Core.IO // ReSharper disable once AssignNullToNotNullAttribute var filepath = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories - ? Path.Combine(folder, filename) + ? Path.Combine(folder, filename).Replace('\\', '/') : folder + "-" + filename; return filepath; diff --git a/src/Umbraco.Core/Models/ContentXmlEntity.cs b/src/Umbraco.Core/Models/ContentXmlEntity.cs index 0450fdc72e..93185834a3 100644 --- a/src/Umbraco.Core/Models/ContentXmlEntity.cs +++ b/src/Umbraco.Core/Models/ContentXmlEntity.cs @@ -42,6 +42,7 @@ namespace Umbraco.Core.Models public Guid Key { get; set; } public DateTime CreateDate { get; set; } public DateTime UpdateDate { get; set; } + public DateTime? DeletedDate { get; set; } /// /// Special case, always return false, this will cause the repositories managing @@ -60,5 +61,7 @@ namespace Umbraco.Core.Models DeepCloneHelper.DeepCloneRefProperties(this, clone); return clone; } + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs index d605759ed1..d4da2676c1 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -101,6 +101,9 @@ namespace Umbraco.Core.Models.EntityBase set { SetPropertyValueAndDetectChanges(value, ref _updateDate, Ps.Value.UpdateDateSelector); } } + [IgnoreDataMember] + public DateTime? DeletedDate { get; set; } + internal virtual void ResetIdentity() { _hasIdentity = false; diff --git a/src/Umbraco.Core/Models/EntityBase/IAggregateRoot.cs b/src/Umbraco.Core/Models/EntityBase/IAggregateRoot.cs index 4298dd9cf4..aacb5185e0 100644 --- a/src/Umbraco.Core/Models/EntityBase/IAggregateRoot.cs +++ b/src/Umbraco.Core/Models/EntityBase/IAggregateRoot.cs @@ -3,7 +3,7 @@ /// /// Marker interface for aggregate roots /// - public interface IAggregateRoot : IEntity + public interface IAggregateRoot : IDeletableEntity { } diff --git a/src/Umbraco.Core/Models/EntityBase/IDeletableEntity.cs b/src/Umbraco.Core/Models/EntityBase/IDeletableEntity.cs new file mode 100644 index 0000000000..42f91b6a6c --- /dev/null +++ b/src/Umbraco.Core/Models/EntityBase/IDeletableEntity.cs @@ -0,0 +1,11 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models.EntityBase +{ + public interface IDeletableEntity : IEntity + { + [DataMember] + DateTime? DeletedDate { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/IEntity.cs b/src/Umbraco.Core/Models/EntityBase/IEntity.cs index 81f5f632ef..059983bb38 100644 --- a/src/Umbraco.Core/Models/EntityBase/IEntity.cs +++ b/src/Umbraco.Core/Models/EntityBase/IEntity.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Models.EntityBase /// /// Guid based Id /// - /// The key is currectly used to store the Unique Id from the + /// The key is currectly used to store the Unique Id from the /// umbracoNode table, which many of the entities are based on. [DataMember] Guid Key { get; set; } diff --git a/src/Umbraco.Core/Models/IPartialView.cs b/src/Umbraco.Core/Models/IPartialView.cs index 01127ce22a..40a760427a 100644 --- a/src/Umbraco.Core/Models/IPartialView.cs +++ b/src/Umbraco.Core/Models/IPartialView.cs @@ -2,6 +2,6 @@ { public interface IPartialView : IFile { - + PartialViewType ViewType { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PartialView.cs b/src/Umbraco.Core/Models/PartialView.cs index 75914820f0..3c7a3ee908 100644 --- a/src/Umbraco.Core/Models/PartialView.cs +++ b/src/Umbraco.Core/Models/PartialView.cs @@ -1,10 +1,8 @@ using System; using System.Runtime.Serialization; -using Umbraco.Core.Services; namespace Umbraco.Core.Models { - /// /// Represents a Partial View file /// @@ -12,14 +10,21 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class PartialView : File, IPartialView { + [Obsolete("Use the ctor that explicitely sets the view type.")] public PartialView(string path) - : this(path, null) + : this(PartialViewType.PartialView, path, null) { } - internal PartialView(string path, Func getFileContent) + public PartialView(PartialViewType viewType, string path) + : this(viewType, path, null) + { } + + internal PartialView(PartialViewType viewType, string path, Func getFileContent) : base(path, getFileContent) - { } + { + ViewType = viewType; + } - internal PartialViewType ViewType { get; set; } + public PartialViewType ViewType { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PartialViewType.cs b/src/Umbraco.Core/Models/PartialViewType.cs index 2b45448271..6204b6e165 100644 --- a/src/Umbraco.Core/Models/PartialViewType.cs +++ b/src/Umbraco.Core/Models/PartialViewType.cs @@ -1,6 +1,6 @@ namespace Umbraco.Core.Models { - internal enum PartialViewType : byte + public enum PartialViewType : byte { Unknown = 0, // default PartialView = 1, diff --git a/src/Umbraco.Core/OrderedHashSet.cs b/src/Umbraco.Core/OrderedHashSet.cs index 2fd545c915..801f1a9a41 100644 --- a/src/Umbraco.Core/OrderedHashSet.cs +++ b/src/Umbraco.Core/OrderedHashSet.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core { private readonly bool _keepOldest; - public OrderedHashSet(bool keepOldest = true) + public OrderedHashSet(bool keepOldest = true) { _keepOldest = keepOldest; } diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs index cd08825e2d..486f84b444 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.Persistence.Migrations Logger = logger; } - public IMigrationContext Context { get; internal set; } + public IMigrationContext Context; public abstract void Up(); public abstract void Down(); diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs index 4865a77ab8..b8141711d7 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs @@ -2,6 +2,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero @@ -16,7 +17,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe public override void Up() { - var exists = Context.Database.FirstOrDefault("WHERE alias=@alias", new {alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias}); + var exists = Context.Database.FirstOrDefault("WHERE alias=@alias", new {alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias}); if (exists == null) { Insert.IntoTable("umbracoRelationType").Row(new @@ -28,13 +29,42 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias }); } - - - } public override void Down() + { } + + // need to capture the DTO as it is modified in later migrations + + [TableName("umbracoRelationType")] + [PrimaryKey("id")] + [ExplicitColumns] + internal class RelationTypeDtoCapture { + public const int NodeIdSeed = 3; + + [Column("id")] + [PrimaryKeyColumn(IdentitySeed = NodeIdSeed)] + public int Id { get; set; } + + [Column("dual")] + public bool Dual { get; set; } + + [Column("parentObjectType")] + public Guid ParentObjectType { get; set; } + + [Column("childObjectType")] + public Guid ChildObjectType { get; set; } + + [Column("name")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_name")] + public string Name { get; set; } + + [Column("alias")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(100)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_alias")] + public string Alias { get; set; } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs index 464ae16961..ef3ce3eec2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs @@ -70,6 +70,8 @@ namespace Umbraco.Core.Persistence.Repositories { //Remove 'published' xml from the cmsContentXml table for the unpublished content Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); + + entity.DeletedDate = DateTime.Now; } protected override void PersistNewItem(ContentXmlEntity entity) diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 1ebfb4bc47..1690b36148 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -270,6 +270,8 @@ AND umbracoNode.id <> @id", //Delete (base) node data Database.Delete("WHERE uniqueID = @Id", new { Id = entity.Key }); + + entity.DeletedDate = DateTime.Now; } #endregion @@ -539,6 +541,8 @@ AND umbracoNode.id <> @id", Database.Execute( "DELETE FROM cmsDataTypePreValues WHERE id=@Id", new { Id = entity.Id }); + + entity.DeletedDate = DateTime.Now; } protected override void PersistNewItem(PreValueEntity entity) diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs index 41bdf020cd..968c2d9cb0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs @@ -195,6 +195,8 @@ namespace Umbraco.Core.Persistence.Repositories //Clear the cache entries that exist by uniqueid/item key IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.Key)); + + entity.DeletedDate = DateTime.Now; } private void RecursiveDelete(Guid parentId) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs index b741238fb9..bc23d7201c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -167,6 +167,8 @@ namespace Umbraco.Core.Persistence.Repositories // delete Database.Delete(nodeDto); + + entity.DeletedDate = DateTime.Now; } protected override void PersistNewItem(EntityContainer entity) diff --git a/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs index 63cfd53833..f3380296df 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories var updated = FileSystem.GetLastModified(path).UtcDateTime; //var content = GetFileContent(path); - var view = new PartialView(path, file => GetFileContent(file.OriginalPath)) + var view = new PartialView(ViewType, path, file => GetFileContent(file.OriginalPath)) { //id can be the hash Id = path.GetHashCode(), @@ -38,8 +38,7 @@ namespace Umbraco.Core.Persistence.Repositories //Content = content, CreateDate = created, UpdateDate = updated, - VirtualPath = FileSystem.GetUrl(id), - ViewType = ViewType + VirtualPath = FileSystem.GetUrl(id) }; //on initial construction we don't want to have dirty properties tracked diff --git a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs index dc61da119e..afe1a78b68 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs @@ -74,6 +74,7 @@ namespace Umbraco.Core.Persistence.Repositories { Database.Execute(delete, new { Id = GetEntityId(entity) }); } + entity.DeletedDate = DateTime.Now; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 6bc801da4d..7f265aceb7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -311,6 +311,8 @@ namespace Umbraco.Core.Persistence.Repositories var masterpageName = string.Concat(entity.Alias, ".master"); _masterpagesFileSystem.DeleteFile(masterpageName); } + + entity.DeletedDate = DateTime.Now; } #endregion diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs index 7a2a06cf67..15a7a816bd 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs @@ -20,6 +20,11 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters public override object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview) { + if(source == null) + { + return string.Empty; + } + return source.ToString(); } diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs index c2dfd687dd..ef089a3c22 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs @@ -83,7 +83,7 @@ namespace Umbraco.Core.Services /// public bool HasContainerInPath(string contentPath) { - using (var uow = UowProvider.GetUnitOfWork()) + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { // can use same repo for both content and media var repository = RepositoryFactory.CreateContentTypeRepository(uow); diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index ea926db5b9..ca014c5d8e 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -95,6 +95,16 @@ namespace Umbraco.Core.Services return result.HasValue ? Attempt.Succeed(result.Value) : Attempt.Fail(); } + public Attempt GetIdForUdi(Udi udi) + { + var guidUdi = udi as GuidUdi; + if (guidUdi == null) + return Attempt.Fail(); + + var umbracoType = Constants.UdiEntityType.ToUmbracoObjectType(guidUdi.EntityType); + return GetIdForKey(guidUdi.Guid, umbracoType); + } + /// /// Returns the GUID for a given integer id /// @@ -124,9 +134,9 @@ namespace Umbraco.Core.Services private static Guid GetNodeObjectTypeGuid(UmbracoObjectTypes umbracoObjectType) { - var guid = umbracoObjectType.GetGuid(); + var guid = umbracoObjectType.GetGuid(); if (guid == Guid.Empty) - throw new NotSupportedException("Unsupported object type (" + umbracoObjectType + ")."); + throw new NotSupportedException("Unsupported object type (" + umbracoObjectType + ")."); return guid; } diff --git a/src/Umbraco.Core/Services/ExternalLoginService.cs b/src/Umbraco.Core/Services/ExternalLoginService.cs index 71ad0fcce9..2d2baeeb07 100644 --- a/src/Umbraco.Core/Services/ExternalLoginService.cs +++ b/src/Umbraco.Core/Services/ExternalLoginService.cs @@ -14,8 +14,7 @@ namespace Umbraco.Core.Services { public ExternalLoginService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - } + { } /// /// Returns all user logins assigned @@ -23,30 +22,32 @@ namespace Umbraco.Core.Services /// /// public IEnumerable GetAll(int userId) - { - using (var uow = UowProvider.GetUnitOfWork()) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { + // ToList is important here, must evaluate within uow! var repo = RepositoryFactory.CreateExternalLoginRepository(uow); - var ret = repo.GetByQuery(new Query().Where(x => x.UserId == userId)); - uow.Commit(); - return ret; + return repo.GetByQuery(new Query() + .Where(x => x.UserId == userId)) + .ToList(); } } /// - /// Returns all logins matching the login info - generally there should only be one but in some cases + /// Returns all logins matching the login info - generally there should only be one but in some cases /// there might be more than one depending on if an adminstrator has been editing/removing members /// /// /// public IEnumerable Find(UserLoginInfo login) { - using (var uow = UowProvider.GetUnitOfWork()) + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { + // ToList is important here, must evaluate within uow! var repo = RepositoryFactory.CreateExternalLoginRepository(uow); - var ret = repo.GetByQuery(new Query().Where(x => x.ProviderKey == login.ProviderKey && x.LoginProvider == login.LoginProvider)); - uow.Commit(); - return ret; + return repo.GetByQuery(new Query() + .Where(x => x.ProviderKey == login.ProviderKey && x.LoginProvider == login.LoginProvider)) + .ToList(); } } @@ -78,7 +79,5 @@ namespace Umbraco.Core.Services uow.Commit(); } } - - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IEntityService.cs b/src/Umbraco.Core/Services/IEntityService.cs index 46200287a5..82e5227cf2 100644 --- a/src/Umbraco.Core/Services/IEntityService.cs +++ b/src/Umbraco.Core/Services/IEntityService.cs @@ -28,7 +28,14 @@ namespace Umbraco.Core.Services /// /// /// - Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType); + Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType); + + /// + /// Returns the integer id for a given Udi + /// + /// + /// + Attempt GetIdForUdi(Udi udi); /// /// Returns the GUID for a given integer id diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 522e7e732c..22b7fe8225 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -692,11 +692,11 @@ namespace Umbraco.Core.Services } Func createSql = url => new Sql().Select("*") - .From() - .InnerJoin() - .On(left => left.PropertyTypeId, right => right.Id) - .Where(x => x.Alias == "umbracoFile") - .Where(x => x.VarChar == url); + .From() + .InnerJoin() + .On(left => left.PropertyTypeId, right => right.Id) + .Where(x => x.Alias == "umbracoFile") + .Where(x => x.VarChar == url); var sql = createSql(umbracoFileValue); @@ -899,8 +899,14 @@ namespace Umbraco.Core.Services throw new ArgumentException("Cannot save media with empty name."); } - var repository = RepositoryFactory.CreateMediaRepository(uow); - media.CreatorId = userId; + var repository = RepositoryFactory.CreateMediaRepository(uow); + + //set the creator id if it's new + if (media.HasIdentity == false) + { + media.CreatorId = userId; + } + repository.AddOrUpdate(media); repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); // generate preview for blame history? @@ -1048,7 +1054,7 @@ namespace Umbraco.Core.Services /// Optional id of the user deleting the media public void DeleteMediaOfType(int mediaTypeId, int userId = 0) { - DeleteMediaOfTypes(new[] {mediaTypeId}, userId); + DeleteMediaOfTypes(new[] { mediaTypeId }, userId); } /// diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index fbf3a7ee8f..41314d65c8 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -89,18 +89,18 @@ namespace Umbraco.Core.Services /// public string FetchPackageFile(Guid packageId, Version umbracoVersion, int userId) { - var packageRepo = UmbracoConfig.For.UmbracoSettings().PackageRepositories.GetDefault(); + var packageRepo = UmbracoConfig.For.UmbracoSettings().PackageRepositories.GetDefault(); using (var httpClient = new HttpClient()) using (var uow = _uowProvider.GetUnitOfWork()) - { + { //includeHidden = true because we don't care if it's hidden we want to get the file regardless var url = string.Format("{0}/{1}?version={2}&includeHidden=true&asFile=true", packageRepo.RestApiUrl, packageId, umbracoVersion.ToString(3)); byte[] bytes; - try - { - bytes = httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult(); - } + try + { + bytes = httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult(); + } catch (HttpRequestException ex) { throw new ConnectionException("An error occuring downloading the package from " + url, ex); @@ -109,20 +109,20 @@ namespace Umbraco.Core.Services //successfull if (bytes.Length > 0) { - var packagePath = IOHelper.MapPath(SystemDirectories.Packages); + var packagePath = IOHelper.MapPath(SystemDirectories.Packages); // Check for package directory if (Directory.Exists(packagePath) == false) Directory.CreateDirectory(packagePath); - var packageFilePath = Path.Combine(packagePath, packageId + ".umb"); + var packageFilePath = Path.Combine(packagePath, packageId + ".umb"); using (var fs1 = new FileStream(packageFilePath, FileMode.Create)) { fs1.Write(bytes, 0, bytes.Length); return "packages\\" + packageId + ".umb"; } - } + } Audit(uow, AuditType.PackagerInstall, string.Format("Package {0} fetched from {1}", packageId, packageRepo.Id), userId, -1); return null; @@ -133,7 +133,7 @@ namespace Umbraco.Core.Services { var auditRepo = _repositoryFactory.CreateAuditRepository(uow); auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); - } + } #region Content @@ -285,6 +285,7 @@ namespace Umbraco.Core.Services var nodeName = element.Attribute("nodeName").Value; var path = element.Attribute("path").Value; var template = element.Attribute("template").Value; + var key = Guid.Empty; var properties = from property in element.Elements() where property.Attribute("isDoc") == null @@ -302,6 +303,12 @@ namespace Umbraco.Core.Services SortOrder = int.Parse(sortOrder) }; + if (Guid.TryParse(element.Attribute("key").Value, out key)) + { + // update the Guid (for UDI support) + content.Key = key; + } + foreach (var property in properties) { string propertyTypeAlias = isLegacySchema ? property.Attribute("alias").Value : property.Name.LocalName; diff --git a/src/Umbraco.Core/StringUdi.cs b/src/Umbraco.Core/StringUdi.cs index 7f1189677f..59eb40af7e 100644 --- a/src/Umbraco.Core/StringUdi.cs +++ b/src/Umbraco.Core/StringUdi.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Linq; namespace Umbraco.Core { @@ -20,7 +21,7 @@ namespace Umbraco.Core /// The entity type part of the udi. /// The string id part of the udi. public StringUdi(string entityType, string id) - : base(entityType, "umb://" + entityType + "/" + id) + : base(entityType, "umb://" + entityType + "/" + EscapeUriString(id)) { Id = id; } @@ -32,7 +33,20 @@ namespace Umbraco.Core public StringUdi(Uri uriValue) : base(uriValue) { - Id = uriValue.AbsolutePath.TrimStart('/'); + Id = Uri.UnescapeDataString(uriValue.AbsolutePath.TrimStart('/')); + } + + private static string EscapeUriString(string s) + { + // Uri.EscapeUriString preserves / but also [ and ] which is bad + // Uri.EscapeDataString does not preserve / which is bad + + // reserved = : / ? # [ ] @ ! $ & ' ( ) * + , ; = + // unreserved = alpha digit - . _ ~ + + // we want to preserve the / and the unreserved + // so... + return string.Join("/", s.Split('/').Select(Uri.EscapeDataString)); } /// diff --git a/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs b/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs index 8245491d97..841a229a33 100644 --- a/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs +++ b/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs @@ -4,6 +4,7 @@ using System.Web; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; +using Umbraco.Core.Logging; namespace Umbraco.Core.Sync { @@ -40,8 +41,9 @@ namespace Umbraco.Core.Sync .ToList(); if (serversA.Length == 0) - { + { _serverRole = ServerRole.Unknown; // config error, actually + LogHelper.Debug(string.Format("Server Role Unknown: DistributedCalls are enabled but no servers are listed")); } else { @@ -50,7 +52,10 @@ namespace Umbraco.Core.Sync var serverName = master.ServerName; if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace()) + { _serverRole = ServerRole.Unknown; // config error, actually + LogHelper.Debug(string.Format("Server Role Unknown: Server Name or AppId missing from Server configuration in DistributedCalls settings")); + } else _serverRole = IsCurrentServer(appId, serverName) ? ServerRole.Master diff --git a/src/Umbraco.Core/TypeExtensions.cs b/src/Umbraco.Core/TypeExtensions.cs index 76dc79c219..84d2a2d47a 100644 --- a/src/Umbraco.Core/TypeExtensions.cs +++ b/src/Umbraco.Core/TypeExtensions.cs @@ -166,8 +166,8 @@ namespace Umbraco.Core return true; } return false; - } - + } + /// /// Determines whether [is of generic type] [the specified type]. /// diff --git a/src/Umbraco.Core/TypeHelper.cs b/src/Umbraco.Core/TypeHelper.cs index 14c441dcd5..166b7e308f 100644 --- a/src/Umbraco.Core/TypeHelper.cs +++ b/src/Umbraco.Core/TypeHelper.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Reflection; @@ -15,11 +16,51 @@ namespace Umbraco.Core { private static readonly ConcurrentDictionary, PropertyInfo[]> GetPropertiesCache = new ConcurrentDictionary, PropertyInfo[]>(); - private static readonly ConcurrentDictionary GetFieldsCache + private static readonly ConcurrentDictionary GetFieldsCache = new ConcurrentDictionary(); private static readonly Assembly[] EmptyAssemblies = new Assembly[0]; - + + /// + /// Based on a type we'll check if it is IEnumerable{T} (or similar) and if so we'll return a List{T}, this will also deal with array types and return List{T} for those too. + /// If it cannot be done, null is returned. + /// + /// + /// + internal static IList CreateGenericEnumerableFromObject(object obj) + { + var type = obj.GetType(); + + if (type.IsGenericType) + { + var genericTypeDef = type.GetGenericTypeDefinition(); + + if (genericTypeDef == typeof(IEnumerable<>) + || genericTypeDef == typeof(ICollection<>) + || genericTypeDef == typeof(Collection<>) + || genericTypeDef == typeof(IList<>) + || genericTypeDef == typeof(List<>) + //this will occur when Linq is used and we get the odd WhereIterator or DistinctIterators since those are special iterator types + || obj is IEnumerable) + { + //if it is a IEnumerable<>, IList or ICollection<> we'll use a List<> + var genericType = typeof(List<>).MakeGenericType(type.GetGenericArguments()); + //pass in obj to fill the list + return (IList)Activator.CreateInstance(genericType, obj); + } + } + + if (type.IsArray) + { + //if its an array, we'll use a List<> + var genericType = typeof(List<>).MakeGenericType(type.GetElementType()); + //pass in obj to fill the list + return (IList)Activator.CreateInstance(genericType, obj); + } + + return null; + } + /// /// Checks if the method is actually overriding a base method /// @@ -45,8 +86,8 @@ namespace Umbraco.Core if (assembly.IsAppCodeAssembly() || assembly.IsGlobalAsaxAssembly()) return EmptyAssemblies; - - // find all assembly references that are referencing the current type's assembly since we + + // find all assembly references that are referencing the current type's assembly since we // should only be scanning those assemblies because any other assembly will definitely not // contain sub type's of the one we're currently looking for var name = assembly.GetName().Name; diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index 9d67c6ccab..142bf025a9 100644 --- a/src/Umbraco.Core/Udi.cs +++ b/src/Umbraco.Core/Udi.cs @@ -101,7 +101,8 @@ namespace Umbraco.Core public override string ToString() { // UriValue is created in the ctor and is never null - return UriValue.ToString(); + // use AbsoluteUri here and not ToString else it's not encoded! + return UriValue.AbsoluteUri; } /// @@ -159,7 +160,7 @@ namespace Umbraco.Core } if (udiType == UdiType.StringUdi) { - udi = path == string.Empty ? GetRootUdi(uri.Host) : new StringUdi(uri.Host, path); + udi = path == string.Empty ? GetRootUdi(uri.Host) : new StringUdi(uri.Host, Uri.UnescapeDataString(path)); return true; } if (tryParse) return false; diff --git a/src/Umbraco.Core/UdiGetterExtensions.cs b/src/Umbraco.Core/UdiGetterExtensions.cs index 795d48c259..3d829c8a1a 100644 --- a/src/Umbraco.Core/UdiGetterExtensions.cs +++ b/src/Umbraco.Core/UdiGetterExtensions.cs @@ -198,7 +198,13 @@ namespace Umbraco.Core public static StringUdi GetUdi(this IPartialView entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(Constants.UdiEntityType.PartialView, entity.Path.TrimStart('/')).EnsureClosed(); + + // we should throw on Unknown but for the time being, assume it means PartialView + var entityType = entity.ViewType == PartialViewType.PartialViewMacro + ? Constants.UdiEntityType.PartialViewMacro + : Constants.UdiEntityType.PartialView; + + return new StringUdi(entityType, entity.Path.TrimStart('/')).EnsureClosed(); } /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ecdd7a5cfc..3e52efdb65 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -100,9 +100,8 @@ ..\packages\Owin.1.0\lib\net40\Owin.dll True - - ..\packages\Semver.2.0.4\lib\netstandard1.1\Semver.dll - True + + ..\packages\semver.1.1.2\lib\net45\Semver.dll @@ -239,6 +238,7 @@ + @@ -340,8 +340,11 @@ + + + diff --git a/src/Umbraco.Core/packages.config b/src/Umbraco.Core/packages.config index 191eb11c01..dd00d61ea6 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -15,11 +15,7 @@ - + - - - - \ No newline at end of file diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 6500cd3741..84256a1e9b 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -79,6 +79,7 @@ + diff --git a/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs b/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs index 3aa1db675f..a5036188fd 100644 --- a/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs +++ b/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Cache; +using System.Linq; namespace Umbraco.Tests.Cache { @@ -31,70 +32,70 @@ namespace Umbraco.Tests.Cache new EventDefinition(null, ServiceContext.SectionService, new EventArgs(), "Deleted"), new EventDefinition(null, ServiceContext.SectionService, new EventArgs(), "New"), - new EventDefinition>(null, ServiceContext.UserService, new SaveEventArgs((IUserType) null)), - new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs((IUserType) null)), + new EventDefinition>(null, ServiceContext.UserService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.UserService, new SaveEventArgs((IUser) null)), - new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs((IUser) null)), - new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs((IUser) null)), + new EventDefinition>(null, ServiceContext.UserService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.LocalizationService, new SaveEventArgs((IDictionaryItem) null)), - new EventDefinition>(null, ServiceContext.LocalizationService, new DeleteEventArgs((IDictionaryItem) null)), + new EventDefinition>(null, ServiceContext.LocalizationService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.LocalizationService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.DataTypeService, new SaveEventArgs((IDataTypeDefinition) null)), - new EventDefinition>(null, ServiceContext.DataTypeService, new DeleteEventArgs((IDataTypeDefinition) null)), + new EventDefinition>(null, ServiceContext.DataTypeService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.DataTypeService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.FileService, new SaveEventArgs((Stylesheet) null)), - new EventDefinition>(null, ServiceContext.FileService, new DeleteEventArgs((Stylesheet) null)), + new EventDefinition>(null, ServiceContext.FileService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.FileService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.DomainService, new SaveEventArgs((IDomain) null)), - new EventDefinition>(null, ServiceContext.DomainService, new DeleteEventArgs((IDomain) null)), + new EventDefinition>(null, ServiceContext.DomainService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.DomainService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.LocalizationService, new SaveEventArgs((ILanguage) null)), - new EventDefinition>(null, ServiceContext.LocalizationService, new DeleteEventArgs((ILanguage) null)), + new EventDefinition>(null, ServiceContext.LocalizationService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.LocalizationService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.ContentTypeService, new SaveEventArgs((IContentType) null)), - new EventDefinition>(null, ServiceContext.ContentTypeService, new DeleteEventArgs((IContentType) null)), - new EventDefinition>(null, ServiceContext.ContentTypeService, new SaveEventArgs((IMediaType) null)), - new EventDefinition>(null, ServiceContext.ContentTypeService, new DeleteEventArgs((IMediaType) null)), + new EventDefinition>(null, ServiceContext.ContentTypeService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.ContentTypeService, new DeleteEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.ContentTypeService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.ContentTypeService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.MemberTypeService, new SaveEventArgs((IMemberType) null)), - new EventDefinition>(null, ServiceContext.MemberTypeService, new DeleteEventArgs((IMemberType) null)), + new EventDefinition>(null, ServiceContext.MemberTypeService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.MemberTypeService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.FileService, new SaveEventArgs((ITemplate) null)), - new EventDefinition>(null, ServiceContext.FileService, new DeleteEventArgs((ITemplate) null)), + new EventDefinition>(null, ServiceContext.FileService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.FileService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.MacroService, new SaveEventArgs((IMacro) null)), - new EventDefinition>(null, ServiceContext.MacroService, new DeleteEventArgs((IMacro) null)), + new EventDefinition>(null, ServiceContext.MacroService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.MacroService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.MemberService, new SaveEventArgs((IMember) null)), - new EventDefinition>(null, ServiceContext.MemberService, new DeleteEventArgs((IMember) null)), + new EventDefinition>(null, ServiceContext.MemberService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.MemberService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.MemberGroupService, new SaveEventArgs((IMemberGroup) null)), - new EventDefinition>(null, ServiceContext.MemberGroupService, new DeleteEventArgs((IMemberGroup) null)), + new EventDefinition>(null, ServiceContext.MemberGroupService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.MemberGroupService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.MediaService, new SaveEventArgs((IMedia) null)), - new EventDefinition>(null, ServiceContext.MediaService, new DeleteEventArgs((IMedia) null)), + new EventDefinition>(null, ServiceContext.MediaService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.MediaService, new DeleteEventArgs(Enumerable.Empty())), new EventDefinition>(null, ServiceContext.MediaService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Moved"), new EventDefinition>(null, ServiceContext.MediaService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Trashed"), new EventDefinition(null, ServiceContext.MediaService, new RecycleBinEventArgs(Guid.NewGuid(), new Dictionary>(), true)), - new EventDefinition>(null, ServiceContext.ContentService, new SaveEventArgs((IContent) null)), - new EventDefinition>(null, ServiceContext.ContentService, new DeleteEventArgs((IContent) null)), + new EventDefinition>(null, ServiceContext.ContentService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.ContentService, new DeleteEventArgs(Enumerable.Empty())), new EventDefinition>(null, ServiceContext.ContentService, new CopyEventArgs(null, null, -1)), new EventDefinition>(null, ServiceContext.ContentService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Trashed"), new EventDefinition(null, ServiceContext.ContentService, new RecycleBinEventArgs(Guid.NewGuid(), new Dictionary>(), true)), - new EventDefinition>(null, ServiceContext.ContentService, new PublishEventArgs((IContent) null), "Published"), - new EventDefinition>(null, ServiceContext.ContentService, new PublishEventArgs((IContent) null), "UnPublished"), + new EventDefinition>(null, ServiceContext.ContentService, new PublishEventArgs(Enumerable.Empty()), "Published"), + new EventDefinition>(null, ServiceContext.ContentService, new PublishEventArgs(Enumerable.Empty()), "UnPublished"), - new EventDefinition>(null, ServiceContext.PublicAccessService, new SaveEventArgs((PublicAccessEntry) null)), - new EventDefinition>(null, ServiceContext.PublicAccessService, new DeleteEventArgs((PublicAccessEntry) null)), + new EventDefinition>(null, ServiceContext.PublicAccessService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.PublicAccessService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.RelationService, new SaveEventArgs((IRelationType) null)), - new EventDefinition>(null, ServiceContext.RelationService, new DeleteEventArgs((IRelationType) null)), + new EventDefinition>(null, ServiceContext.RelationService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.RelationService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, ServiceContext.RelationService, new SaveEventArgs((IRelationType) null)), - new EventDefinition>(null, ServiceContext.RelationService, new DeleteEventArgs((IRelationType) null)), + new EventDefinition>(null, ServiceContext.RelationService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ServiceContext.RelationService, new DeleteEventArgs(Enumerable.Empty())), }; foreach (var definition in definitions) diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs index 06dab42556..0287159cd9 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core; @@ -177,6 +178,39 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings public void DisallowedUploadFiles() { Assert.IsTrue(SettingsSection.Content.DisallowedUploadFiles.All(x => "ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd".Split(',').Contains(x))); + } + + [Test] + public void AllowedUploadFiles() + { + Assert.IsTrue(SettingsSection.Content.AllowedUploadFiles.All(x => "jpg,gif,png".Split(',').Contains(x))); + } + + [Test] + [TestCase("png", true)] + [TestCase("jpg", true)] + [TestCase("gif", true)] + // TODO: Why does it flip to TestingDefaults=true for these two tests on AppVeyor. WHY? + //[TestCase("bmp", false)] + //[TestCase("php", false)] + [TestCase("ashx", false)] + [TestCase("config", false)] + public void IsFileAllowedForUpload_WithWhitelist(string extension, bool expected) + { + // Make really sure that defaults are NOT used + TestingDefaults = false; + + Debug.WriteLine("Extension being tested", extension); + Debug.WriteLine("AllowedUploadFiles: {0}", SettingsSection.Content.AllowedUploadFiles); + Debug.WriteLine("DisallowedUploadFiles: {0}", SettingsSection.Content.DisallowedUploadFiles); + + var allowedContainsExtension = SettingsSection.Content.AllowedUploadFiles.Any(x => x.InvariantEquals(extension)); + var disallowedContainsExtension = SettingsSection.Content.DisallowedUploadFiles.Any(x => x.InvariantEquals(extension)); + + Debug.WriteLine("AllowedContainsExtension: {0}", allowedContainsExtension); + Debug.WriteLine("DisallowedContainsExtension: {0}", disallowedContainsExtension); + + Assert.AreEqual(SettingsSection.Content.IsFileAllowedForUpload(extension), expected); } } } diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/UmbracoSettingsTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/UmbracoSettingsTests.cs index 8342bd13a3..01768dd903 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/UmbracoSettingsTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/UmbracoSettingsTests.cs @@ -1,7 +1,7 @@ using System.Configuration; +using System.Diagnostics; using System.IO; using NUnit.Framework; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Tests.TestHelpers; @@ -9,20 +9,17 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings { public abstract class UmbracoSettingsTests { - - protected virtual bool TestingDefaults - { - get { return false; } - } + protected virtual bool TestingDefaults { get; set; } [SetUp] public void Init() { var config = new FileInfo(TestHelper.MapPathForTest("~/Configurations/UmbracoSettings/web.config")); - - var fileMap = new ExeConfigurationFileMap() { ExeConfigFilename = config.FullName }; - var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); + var fileMap = new ExeConfigurationFileMap() { ExeConfigFilename = config.FullName }; + var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); + + Debug.WriteLine("Testing defaults? {0}", TestingDefaults); if (TestingDefaults) { SettingsSection = configuration.GetSection("umbracoConfiguration/defaultSettings") as UmbracoSettingsSection; @@ -32,8 +29,6 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings SettingsSection = configuration.GetSection("umbracoConfiguration/settings") as UmbracoSettingsSection; } - - Assert.IsNotNull(SettingsSection); } diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config b/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config index 80eaee77d3..a6f9826492 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config @@ -100,6 +100,9 @@ ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd + + jpg,png,gif + Textstring diff --git a/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs b/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs index 973acea207..5d3015e2b7 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs @@ -37,14 +37,14 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); var repository = new PartialViewRepository(unitOfWork, _fileSystem); - var partialView = new PartialView("test-path-1.cshtml") { Content = "// partialView" }; + var partialView = new PartialView(PartialViewType.PartialView, "test-path-1.cshtml") { Content = "// partialView" }; repository.AddOrUpdate(partialView); unitOfWork.Commit(); Assert.IsTrue(_fileSystem.FileExists("test-path-1.cshtml")); Assert.AreEqual("test-path-1.cshtml", partialView.Path); Assert.AreEqual("/Views/Partials/test-path-1.cshtml", partialView.VirtualPath); - partialView = new PartialView("path-2/test-path-2.cshtml") { Content = "// partialView" }; + partialView = new PartialView(PartialViewType.PartialView, "path-2/test-path-2.cshtml") { Content = "// partialView" }; repository.AddOrUpdate(partialView); unitOfWork.Commit(); Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-2.cshtml")); @@ -56,7 +56,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual("path-2\\test-path-2.cshtml", partialView.Path); Assert.AreEqual("/Views/Partials/path-2/test-path-2.cshtml", partialView.VirtualPath); - partialView = new PartialView("path-2\\test-path-3.cshtml") { Content = "// partialView" }; + partialView = new PartialView(PartialViewType.PartialView, "path-2\\test-path-3.cshtml") { Content = "// partialView" }; repository.AddOrUpdate(partialView); unitOfWork.Commit(); Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-3.cshtml")); @@ -73,7 +73,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual("path-2\\test-path-3.cshtml", partialView.Path); Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath); - partialView = new PartialView("\\test-path-4.cshtml") { Content = "// partialView" }; + partialView = new PartialView(PartialViewType.PartialView, "\\test-path-4.cshtml") { Content = "// partialView" }; Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \ { repository.AddOrUpdate(partialView); diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs index b68e2a3f9f..825432756b 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs @@ -4,13 +4,16 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core.Events; +using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Scoping; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Scoping { [TestFixture] - public class ScopeEventDispatcherTests + public class ScopeEventDispatcherTests : BaseUmbracoConfigurationTest { [SetUp] public void Setup() @@ -79,7 +82,7 @@ namespace Umbraco.Tests.Scoping var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray(); var knownNames = new[] { "DoThing1", "DoThing2", "DoThing3" }; - var knownArgTypes = new[] { typeof (SaveEventArgs), typeof (SaveEventArgs), typeof (SaveEventArgs) }; + var knownArgTypes = new[] { typeof(SaveEventArgs), typeof(SaveEventArgs), typeof(SaveEventArgs) }; for (var i = 0; i < events.Length; i++) { @@ -89,6 +92,166 @@ namespace Umbraco.Tests.Scoping } } + [Test] + public void SupersededEvents() + { + DoSaveForContent += OnDoThingFail; + DoDeleteForContent += OnDoThingFail; + DoForTestArgs += OnDoThingFail; + DoForTestArgs2 += OnDoThingFail; + + var contentType = MockedContentTypes.CreateBasicContentType(); + + var content1 = MockedContent.CreateBasicContent(contentType); + content1.Id = 123; + + var content2 = MockedContent.CreateBasicContent(contentType); + content2.Id = 456; + + var content3 = MockedContent.CreateBasicContent(contentType); + content3.Id = 789; + + var scopeProvider = new ScopeProvider(Mock.Of()); + using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) + { + + //content1 will be filtered from the args + scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(new[]{ content1 , content3})); + scope.Events.Dispatch(DoDeleteForContent, this, new DeleteEventArgs(content1)); + scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content2)); + //this entire event will be filtered + scope.Events.Dispatch(DoForTestArgs, this, new TestEventArgs(content1)); + scope.Events.Dispatch(DoForTestArgs2, this, new TestEventArgs2(content1)); + + // events have been queued + var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray(); + Assert.AreEqual(4, events.Length); + + Assert.AreEqual(typeof(SaveEventArgs), events[0].Args.GetType()); + Assert.AreEqual(1, ((SaveEventArgs)events[0].Args).SavedEntities.Count()); + Assert.AreEqual(content3.Id, ((SaveEventArgs)events[0].Args).SavedEntities.First().Id); + + Assert.AreEqual(typeof(DeleteEventArgs), events[1].Args.GetType()); + Assert.AreEqual(content1.Id, ((DeleteEventArgs) events[1].Args).DeletedEntities.First().Id); + + Assert.AreEqual(typeof(SaveEventArgs), events[2].Args.GetType()); + Assert.AreEqual(content2.Id, ((SaveEventArgs)events[2].Args).SavedEntities.First().Id); + + Assert.AreEqual(typeof(TestEventArgs2), events[3].Args.GetType()); + } + } + + /// + /// This will test that when we track events that before we Get the events we normalize all of the + /// event entities to be the latest one (most current) found amongst the event so that there is + /// no 'stale' entities in any of the args + /// + [Test] + public void LatestEntities() + { + DoSaveForContent += OnDoThingFail; + + var now = DateTime.Now; + var contentType = MockedContentTypes.CreateBasicContentType(); + var content1 = MockedContent.CreateBasicContent(contentType); + content1.Id = 123; + content1.UpdateDate = now.AddMinutes(1); + var content2 = MockedContent.CreateBasicContent(contentType); + content2.Id = 123; + content2.UpdateDate = now.AddMinutes(2); + var content3 = MockedContent.CreateBasicContent(contentType); + content3.Id = 123; + content3.UpdateDate = now.AddMinutes(3); + + var scopeProvider = new ScopeProvider(Mock.Of()); + using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) + { + scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content1)); + scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content2)); + scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content3)); + + // events have been queued + var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray(); + Assert.AreEqual(3, events.Length); + + foreach (var t in events) + { + var args = (SaveEventArgs)t.Args; + foreach (var entity in args.SavedEntities) + { + Assert.AreEqual(content3, entity); + Assert.IsTrue(object.ReferenceEquals(content3, entity)); + } + } + } + } + + [Test] + public void FirstIn() + { + DoSaveForContent += OnDoThingFail; + + var now = DateTime.Now; + var contentType = MockedContentTypes.CreateBasicContentType(); + var content1 = MockedContent.CreateBasicContent(contentType); + content1.Id = 123; + content1.UpdateDate = now.AddMinutes(1); + var content2 = MockedContent.CreateBasicContent(contentType); + content2.Id = 123; + content1.UpdateDate = now.AddMinutes(2); + var content3 = MockedContent.CreateBasicContent(contentType); + content3.Id = 123; + content1.UpdateDate = now.AddMinutes(3); + + var scopeProvider = new ScopeProvider(Mock.Of()); + using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) + { + scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content1)); + scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content2)); + scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content3)); + + // events have been queued + var events = scope.Events.GetEvents(EventDefinitionFilter.FirstIn).ToArray(); + Assert.AreEqual(1, events.Length); + Assert.AreEqual(content1, ((SaveEventArgs) events[0].Args).SavedEntities.First()); + Assert.IsTrue(object.ReferenceEquals(content1, ((SaveEventArgs)events[0].Args).SavedEntities.First())); + Assert.AreEqual(content1.UpdateDate, ((SaveEventArgs) events[0].Args).SavedEntities.First().UpdateDate); + } + } + + [Test] + public void LastIn() + { + DoSaveForContent += OnDoThingFail; + + var now = DateTime.Now; + var contentType = MockedContentTypes.CreateBasicContentType(); + var content1 = MockedContent.CreateBasicContent(contentType); + content1.Id = 123; + content1.UpdateDate = now.AddMinutes(1); + var content2 = MockedContent.CreateBasicContent(contentType); + content2.Id = 123; + content2.UpdateDate = now.AddMinutes(2); + var content3 = MockedContent.CreateBasicContent(contentType); + content3.Id = 123; + content3.UpdateDate = now.AddMinutes(3); + + var scopeProvider = new ScopeProvider(Mock.Of()); + using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) + { + scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content1)); + scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content2)); + scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content3)); + + // events have been queued + var events = scope.Events.GetEvents(EventDefinitionFilter.LastIn).ToArray(); + Assert.AreEqual(1, events.Length); + Assert.AreEqual(content3, ((SaveEventArgs)events[0].Args).SavedEntities.First()); + Assert.IsTrue(object.ReferenceEquals(content3, ((SaveEventArgs)events[0].Args).SavedEntities.First())); + Assert.AreEqual(content3.UpdateDate, ((SaveEventArgs)events[0].Args).SavedEntities.First().UpdateDate); + } + } + [TestCase(true)] [TestCase(false)] public void EventsDispatching_Passive(bool complete) @@ -177,12 +340,41 @@ namespace Umbraco.Tests.Scoping Assert.Fail(); } + public static event EventHandler> DoSaveForContent; + public static event EventHandler> DoDeleteForContent; + public static event EventHandler DoForTestArgs; + public static event EventHandler DoForTestArgs2; public static event EventHandler> DoThing1; public static event EventHandler> DoThing2; public static event TypedEventHandler> DoThing3; + public class TestEventArgs : CancellableObjectEventArgs + { + public TestEventArgs(object eventObject) : base(eventObject) + { + } + + public object MyEventObject + { + get { return EventObject; } + } + } + + [SupersedeEvent(typeof(TestEventArgs))] + public class TestEventArgs2 : CancellableObjectEventArgs + { + public TestEventArgs2(object eventObject) : base(eventObject) + { + } + + public object MyEventObject + { + get { return EventObject; } + } + } + public class PassiveEventDispatcher : ScopeEventDispatcherBase { public PassiveEventDispatcher() diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index 6652c3f2a2..ff45a3c17c 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -179,7 +179,9 @@ namespace Umbraco.Tests.Scoping scope.Complete(); } - Assert.AreEqual(complete ? 2 : 0, evented); + //The reason why there is only 1 event occuring is because we are publishing twice for the same event for the same + //object and the scope deduplicates the events (uses the latest) + Assert.AreEqual(complete ? 1 : 0, evented); // this should never change Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml); diff --git a/src/Umbraco.Tests/UdiTests.cs b/src/Umbraco.Tests/UdiTests.cs index 9b803d5fa3..24db54a98f 100644 --- a/src/Umbraco.Tests/UdiTests.cs +++ b/src/Umbraco.Tests/UdiTests.cs @@ -31,6 +31,57 @@ namespace Umbraco.Tests Assert.AreEqual("umb://" + Constants.UdiEntityType.AnyString + "/test-id", udi.ToString()); } + [Test] + public void StringEncodingTest() + { + // absolute path is unescaped + var uri = new Uri("umb://" + Constants.UdiEntityType.AnyString + "/this%20is%20a%20test"); + Assert.AreEqual("umb://" + Constants.UdiEntityType.AnyString + "/this is a test", uri.ToString()); + Assert.AreEqual("umb://" + Constants.UdiEntityType.AnyString + "/this%20is%20a%20test", uri.AbsoluteUri); + Assert.AreEqual("/this%20is%20a%20test", uri.AbsolutePath); + + Assert.AreEqual("/this is a test", Uri.UnescapeDataString(uri.AbsolutePath)); + Assert.AreEqual("%2Fthis%20is%20a%20test", Uri.EscapeDataString("/this is a test")); + Assert.AreEqual("/this%20is%20a%20test", Uri.EscapeUriString("/this is a test")); + + var udi = Udi.Parse("umb://" + Constants.UdiEntityType.AnyString + "/this%20is%20a%20test"); + Assert.AreEqual(Constants.UdiEntityType.AnyString, udi.EntityType); + Assert.IsInstanceOf(udi); + var stringEntityId = udi as StringUdi; + Assert.IsNotNull(stringEntityId); + Assert.AreEqual("this is a test", stringEntityId.Id); + Assert.AreEqual("umb://" + Constants.UdiEntityType.AnyString + "/this%20is%20a%20test", udi.ToString()); + + var udi2 = new StringUdi(Constants.UdiEntityType.AnyString, "this is a test"); + Assert.AreEqual(udi, udi2); + + var udi3 = new StringUdi(Constants.UdiEntityType.AnyString, "path to/this is a test.xyz"); + Assert.AreEqual("umb://" + Constants.UdiEntityType.AnyString + "/path%20to/this%20is%20a%20test.xyz", udi3.ToString()); + } + + [Test, Ignore] + public void StringEncodingTest2() + { + // reserved = : / ? # [ ] @ ! $ & ' ( ) * + , ; = + // unreserved = alpha digit - . _ ~ + + Assert.AreEqual("%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2B%2C%3B%3D.-_~%25", Uri.EscapeDataString(":/?#[]@!$&'()+,;=.-_~%")); + Assert.AreEqual(":/?#[]@!$&'()+,;=.-_~%25", Uri.EscapeUriString(":/?#[]@!$&'()+,;=.-_~%")); + + // we cannot have reserved chars at random places + // we want to keep the / in string udis + + var r = string.Join("/", "path/to/View[1].cshtml".Split('/').Select(Uri.EscapeDataString)); + Assert.AreEqual("path/to/View%5B1%5D.cshtml", r); + Assert.IsTrue(Uri.IsWellFormedUriString("umb://partial-view-macro/" + r, UriKind.Absolute)); + + // with the proper fix in StringUdi this should work: + var udi1 = new StringUdi("partial-view-macro", "path/to/View[1].cshtml"); + Assert.AreEqual("umb://partial-view-macro/path/to/View%5B1%5D.cshtml", udi1.ToString()); + var udi2 = Udi.Parse("umb://partial-view-macro/path/to/View%5B1%5D.cshtml"); + Assert.AreEqual("path/to/View[1].cshtml", ((StringUdi) udi2).Id); + } + [Test] public void GuidEntityCtorTest() { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index c0acf99f83..eb57d210fc 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -97,9 +97,8 @@ ..\packages\Owin.1.0\lib\net40\Owin.dll - - ..\packages\Semver.2.0.4\lib\netstandard1.1\Semver.dll - True + + ..\packages\semver.1.1.2\lib\net45\Semver.dll diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index 63a6c4b259..4458fd06c5 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -21,9 +21,10 @@ + - + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js index 2859cd8a8a..e47032fed3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js @@ -105,14 +105,14 @@ angular.module("umbraco.directives") }); //// INIT ///// - $image.load(function(){ - $timeout(function(){ - setDimensions(); - scope.loaded = true; - if (scope.onImageLoaded) { - scope.onImageLoaded(); - } - }); + $image.load(function() { + $timeout(function() { + setDimensions(); + scope.loaded = true; + if (angular.isFunction(scope.onImageLoaded)) { + scope.onImageLoaded(); + } + }); }); $(window).on('resize.umbImageGravity', function(){ diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 4960a11f19..c0bd7a4eff 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -134,25 +134,42 @@ Use this directive to generate a thumbnail grid of media items. } function setItemData(item) { - item.isFolder = !mediaHelper.hasFilePropertyType(item); + + // check if item is a folder + if(item.image) { + // if is has an image path, it is not a folder + item.isFolder = false; + } else { + item.isFolder = !mediaHelper.hasFilePropertyType(item); + } + if (!item.isFolder) { - item.thumbnail = mediaHelper.resolveFile(item, true); - item.image = mediaHelper.resolveFile(item, false); + + // handle entity + if(item.image) { + item.thumbnail = mediaHelper.resolveFileFromEntity(item, true); + item.extension = mediaHelper.getFileExtension(item.image); + // handle full media object + } else { + item.thumbnail = mediaHelper.resolveFile(item, true); + item.image = mediaHelper.resolveFile(item, false); + + var fileProp = _.find(item.properties, function (v) { + return (v.alias === "umbracoFile"); + }); - var fileProp = _.find(item.properties, function (v) { - return (v.alias === "umbracoFile"); - }); + if (fileProp && fileProp.value) { + item.file = fileProp.value; + } - if (fileProp && fileProp.value) { - item.file = fileProp.value; - } + var extensionProp = _.find(item.properties, function (v) { + return (v.alias === "umbracoExtension"); + }); - var extensionProp = _.find(item.properties, function (v) { - return (v.alias === "umbracoExtension"); - }); + if (extensionProp && extensionProp.value) { + item.extension = extensionProp.value; + } - if (extensionProp && extensionProp.value) { - item.extension = extensionProp.value; } } } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js index 8059975fc1..d74c7d82ac 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js @@ -68,7 +68,7 @@ function logResource($q, $http, umbRequestHelper) { "logApiBaseUrl", "GetCurrentUserLog", [{ logtype: type, sinceDate: since }])), - 'Failed to retrieve user data for id ' + id); + 'Failed to retrieve log data for current user of type ' + type + ' since ' + since); }, /** @@ -99,7 +99,7 @@ function logResource($q, $http, umbRequestHelper) { "logApiBaseUrl", "GetLog", [{ logtype: type, sinceDate: since }])), - 'Failed to retrieve user data for id ' + id); + 'Failed to retrieve log data of type ' + type + ' since ' + since); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js index 114e1a0962..636437e387 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js @@ -365,6 +365,28 @@ function mediaHelper(umbRequestHelper) { return newFileTypesArray.join(","); + }, + + /** + * @ngdoc function + * @name umbraco.services.mediaHelper#getFileExtension + * @methodOf umbraco.services.mediaHelper + * @function + * + * @description + * Returns file extension + * + * @param {string} filePath File path, ex /media/1234/my-image.jpg + */ + getFileExtension: function(filePath) { + + if (!filePath) { + return false; + } + + var lowered = filePath.toLowerCase(); + var ext = lowered.substr(lowered.lastIndexOf(".") + 1); + return ext; } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index 67df26d50a..a3d1e5b0c6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -103,15 +103,15 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ * @description * This returns a promise with an underlying http call, it is a helper method to reduce * the amount of duplicate code needed to query http resources and automatically handle any - * 500 Http server errors. + * Http errors. See /docs/source/using-promises-resources.md * - * @param {object} opts A mixed object which can either be a `string` representing the error message to be - * returned OR an `object` containing either: + * @param {object} opts A mixed object which can either be a string representing the error message to be + * returned OR an object containing either: * { success: successCallback, errorMsg: errorMessage } * OR * { success: successCallback, error: errorCallback } - * In both of the above, the successCallback must accept these parameters: `data`, `status`, `headers`, `config` - * If using the errorCallback it must accept these parameters: `data`, `status`, `headers`, `config` + * In both of the above, the successCallback must accept these parameters: data, status, headers, config + * If using the errorCallback it must accept these parameters: data, status, headers, config * The success callback must return the data which will be resolved by the deferred object. * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status } */ diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html b/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html index ec45b652c9..4edf2eda25 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html @@ -5,7 +5,7 @@

- To read a report of changes between your current version {{installer.current.model.currentVersion}} and this version your upgrading to {{installer.current.model.newVersion}} + To read a report of changes between your current version {{installer.current.model.currentVersion}} and this version you're upgrading to {{installer.current.model.newVersion}}

View Report diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less index f2815f445c..df1bc4114a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/tree.less @@ -49,7 +49,7 @@ .umb-tree li.current > div i.icon, .umb-tree li.current > div ins { color: @white !important; - background: @turquoise-d1; + background-color: @turquoise-d1; border-color: @turquoise-d1; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html index 3e40c79b7d..70a4ccb8b2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html @@ -6,7 +6,8 @@ placeholder="@general_url" class="umb-editor umb-textstring" ng-model="model.target.url" - ng-disabled="model.target.id"/> + ng-disabled="model.target.id" + focus-when="{{true}} "/> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index 7010a0db9d..1992bed068 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -16,14 +16,22 @@ angular.module("umbraco") $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; $scope.cropSize = dialogOptions.cropSize; $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); + + var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; + var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); if ($scope.onlyImages) { - $scope.acceptedFileTypes = mediaHelper - .formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); + $scope.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes); } else { - $scope.acceptedFileTypes = !mediaHelper - .formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles); + // Use whitelist of allowed file types if provided + if (allowedUploadFiles !== '') { + $scope.acceptedFileTypes = allowedUploadFiles; + } else { + // If no whitelist, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles + $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles); + } } - $scope.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; + + $scope.maxFileSize = umbracoSettings.maxFileSize + "KB"; $scope.model.selectedImages = []; @@ -131,8 +139,16 @@ angular.module("umbraco") } else { eventsService.emit("dialogs.mediaPicker.select", image); if ($scope.showDetails) { + $scope.target = image; - $scope.target.url = mediaHelper.resolveFile(image); + + // handle both entity and full media object + if(image.image) { + $scope.target.url = image.image; + } else { + $scope.target.url = mediaHelper.resolveFile(image); + } + $scope.openDetailsDialog(); } else { selectImage(image); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js index 979428de0c..ea9ad6ca97 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js @@ -1,14 +1,13 @@ -(function () { +(function() { "use strict"; function QueryBuilderOverlayController($scope, templateQueryResource, localizationService) { - var everything = localizationService.localize("template_allContent"); - var myWebsite = localizationService.localize("template_websiteRoot"); + var everything = ""; + var myWebsite = ""; + var ascendingTranslation = ""; + var descendingTranslation = ""; - var ascendingTranslation = localizationService.localize("template_ascending"); - var descendingTranslation = localizationService.localize("template_descending"); - var vm = this; vm.properties = []; @@ -21,34 +20,6 @@ format: "YYYY-MM-DD" }; - vm.query = { - contentType: { - name: everything - }, - source: { - name: myWebsite - }, - filters: [ - { - property: undefined, - operator: undefined - } - ], - sort: { - property: { - alias: "", - name: "", - }, - direction: "ascending", //This is the value for sorting sent to server - translation: { - currentLabel: ascendingTranslation, //This is the localized UI value in the the dialog - ascending: ascendingTranslation, - descending: descendingTranslation - } - - } - }; - vm.chooseSource = chooseSource; vm.getPropertyOperators = getPropertyOperators; vm.addFilter = addFilter; @@ -63,21 +34,48 @@ function onInit() { + vm.query = { + contentType: { + name: everything + }, + source: { + name: myWebsite + }, + filters: [ + { + property: undefined, + operator: undefined + } + ], + sort: { + property: { + alias: "", + name: "", + }, + direction: "ascending", //This is the value for sorting sent to server + translation: { + currentLabel: ascendingTranslation, //This is the localized UI value in the the dialog + ascending: ascendingTranslation, + descending: descendingTranslation + } + } + }; + templateQueryResource.getAllowedProperties() - .then(function (properties) { + .then(function(properties) { vm.properties = properties; }); templateQueryResource.getContentTypes() - .then(function (contentTypes) { + .then(function(contentTypes) { vm.contentTypes = contentTypes; }); templateQueryResource.getFilterConditions() - .then(function (conditions) { + .then(function(conditions) { vm.conditions = conditions; }); - + throttledFunc(); } @@ -111,10 +109,11 @@ } function getPropertyOperators(property) { - var conditions = _.filter(vm.conditions, function (condition) { - var index = condition.appliesTo.indexOf(property.type); - return index >= 0; - }); + var conditions = _.filter(vm.conditions, + function(condition) { + var index = condition.appliesTo.indexOf(property.type); + return index >= 0; + }); return conditions; } @@ -123,10 +122,8 @@ } function trashFilter(query, filter) { - for (var i = 0; i < query.filters.length; i++) - { - if (query.filters[i] == filter) - { + for (var i = 0; i < query.filters.length; i++) { + if (query.filters[i] == filter) { query.filters.splice(i, 1); } } @@ -173,7 +170,7 @@ function setFilterTerm(filter, term) { filter.term = term; - if(filter.constraintValue) { + if (filter.constraintValue) { throttledFunc(); } } @@ -183,22 +180,32 @@ } function datePickerChange(event, filter) { - if(event.date && event.date.isValid()) { + if (event.date && event.date.isValid()) { filter.constraintValue = event.date.format(vm.datePickerConfig.format); throttledFunc(); } } - var throttledFunc = _.throttle(function () { - - templateQueryResource.postTemplateQuery(vm.query) - .then(function (response) { - $scope.model.result = response; - }); + var throttledFunc = _.throttle(function() { - }, 200); + templateQueryResource.postTemplateQuery(vm.query) + .then(function(response) { + $scope.model.result = response; + }); - onInit(); + }, + 200); + + localizationService.localizeMany([ + "template_allContent", "template_websiteRoot", "template_ascending", "template_descending" + ]) + .then(function(res) { + everything = res[0]; + myWebsite = res[1]; + ascendingTranslation = res[2]; + descendingTranslation = res[3]; + onInit(); + }); } angular.module("umbraco").controller("Umbraco.Overlays.QueryBuilderController", QueryBuilderOverlayController); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html index f30851e62e..412a57288d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html @@ -1,5 +1,5 @@

-
+
@@ -12,10 +12,10 @@
- {{item.name}} + {{item.name}} - {{item.name}} + {{item.name}} {{item.name}} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index df21541f09..3c8170e54b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -138,15 +138,15 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag //cannot just check for !newVal because it might be an empty string which we //want to look for. if (newVal !== null && newVal !== undefined && newVal !== oldVal) { - //now we need to check if we need to re-initialize our structure which is kind of tricky - // since we only want to do that if the server has changed the value, not if this controller - // has changed the value. There's only 2 scenarios where we change the value internall so - // we know what those values can be, if they are not either of them, then we'll re-initialize. - - if (newVal.clearFiles !== true && newVal !== $scope.originalValue && !newVal.selectedFiles) { + // here we need to check if the value change needs to trigger an update in the UI. + // if the value is only changed in the controller and not in the server values, we do not + // want to trigger an update yet. + // we can however no longer rely on checking values in the controller vs. values from the server + // to determine whether to update or not, since you could potentially be uploading a file with + // the exact same name - in that case we need to reinitialize to show the newly uploaded file. + if (newVal.clearFiles !== true && !newVal.selectedFiles) { initialize($scope.rebuildInput.index + 1); } - } }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index e289a389cb..bb580a906d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -1,10 +1,17 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.Grid.MediaController", - function ($scope, $rootScope, $timeout) { + function ($scope, $rootScope, $timeout, userService) { + + if (!$scope.model.config.startNodeId) { + userService.getCurrentUser().then(function (userData) { + $scope.model.config.startNodeId = userData.startMediaId; + }); + } $scope.setImage = function(){ $scope.mediaPickerOverlay = {}; $scope.mediaPickerOverlay.view = "mediapicker"; + $scope.mediaPickerOverlay.startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined; $scope.mediaPickerOverlay.showDetails = true; $scope.mediaPickerOverlay.disableFolderSelect = true; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js index b8ba4f880b..9a28627aa1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js @@ -6,118 +6,124 @@ * @description * The controller for the content type editor */ -(function() { - "use strict"; +(function () { + "use strict"; - function ListViewGridLayoutController($scope, $routeParams, mediaHelper, mediaResource, $location, listViewHelper, mediaTypeHelper) { + function ListViewGridLayoutController($scope, $routeParams, mediaHelper, mediaResource, $location, listViewHelper, mediaTypeHelper) { - var vm = this; + var vm = this; + var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; - vm.nodeId = $scope.contentId; - //we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles - vm.acceptedFileTypes = !mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles); - vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; - vm.activeDrag = false; - vm.mediaDetailsTooltip = {}; - vm.itemsWithoutFolders = []; - vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; - vm.acceptedMediatypes = []; + vm.nodeId = $scope.contentId; + // Use whitelist of allowed file types if provided + vm.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); + if (vm.acceptedFileTypes === '') { + // If not provided, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles + vm.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles); + } - vm.dragEnter = dragEnter; - vm.dragLeave = dragLeave; - vm.onFilesQueue = onFilesQueue; - vm.onUploadComplete = onUploadComplete; + vm.maxFileSize = umbracoSettings.maxFileSize + "KB"; + vm.activeDrag = false; + vm.mediaDetailsTooltip = {}; + vm.itemsWithoutFolders = []; + vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; + vm.acceptedMediatypes = []; - vm.hoverMediaItemDetails = hoverMediaItemDetails; - vm.selectContentItem = selectContentItem; - vm.selectItem = selectItem; - vm.selectFolder = selectFolder; - vm.goToItem = goToItem; + vm.dragEnter = dragEnter; + vm.dragLeave = dragLeave; + vm.onFilesQueue = onFilesQueue; + vm.onUploadComplete = onUploadComplete; - function activate() { - vm.itemsWithoutFolders = filterOutFolders($scope.items); + vm.hoverMediaItemDetails = hoverMediaItemDetails; + vm.selectContentItem = selectContentItem; + vm.selectItem = selectItem; + vm.selectFolder = selectFolder; + vm.goToItem = goToItem; - //no need to make another REST/DB call if this data is not used when we are browsing the bin - if ($scope.entityType === 'media' && !vm.isRecycleBin) { - mediaTypeHelper.getAllowedImagetypes(vm.nodeId).then(function (types) { - vm.acceptedMediatypes = types; - }); - } + function activate() { + vm.itemsWithoutFolders = filterOutFolders($scope.items); - } + //no need to make another REST/DB call if this data is not used when we are browsing the bin + if ($scope.entityType === 'media' && !vm.isRecycleBin) { + mediaTypeHelper.getAllowedImagetypes(vm.nodeId).then(function (types) { + vm.acceptedMediatypes = types; + }); + } - function filterOutFolders(items) { + } - var newArray = []; + function filterOutFolders(items) { - if(items && items.length ) { + var newArray = []; - for (var i = 0; items.length > i; i++) { - var item = items[i]; - var isFolder = !mediaHelper.hasFilePropertyType(item); + if (items && items.length) { - if (!isFolder) { - newArray.push(item); - } - } + for (var i = 0; items.length > i; i++) { + var item = items[i]; + var isFolder = !mediaHelper.hasFilePropertyType(item); - } + if (!isFolder) { + newArray.push(item); + } + } - return newArray; - } + } - function dragEnter(el, event) { - vm.activeDrag = true; - } + return newArray; + } - function dragLeave(el, event) { - vm.activeDrag = false; - } + function dragEnter(el, event) { + vm.activeDrag = true; + } - function onFilesQueue() { - vm.activeDrag = false; - } + function dragLeave(el, event) { + vm.activeDrag = false; + } - function onUploadComplete() { - $scope.getContent($scope.contentId); - } + function onFilesQueue() { + vm.activeDrag = false; + } - function hoverMediaItemDetails(item, event, hover) { + function onUploadComplete() { + $scope.getContent($scope.contentId); + } - if (hover && !vm.mediaDetailsTooltip.show) { + function hoverMediaItemDetails(item, event, hover) { - vm.mediaDetailsTooltip.event = event; - vm.mediaDetailsTooltip.item = item; - vm.mediaDetailsTooltip.show = true; + if (hover && !vm.mediaDetailsTooltip.show) { - } else if (!hover && vm.mediaDetailsTooltip.show) { + vm.mediaDetailsTooltip.event = event; + vm.mediaDetailsTooltip.item = item; + vm.mediaDetailsTooltip.show = true; - vm.mediaDetailsTooltip.show = false; + } else if (!hover && vm.mediaDetailsTooltip.show) { - } + vm.mediaDetailsTooltip.show = false; - } + } - function selectContentItem(item, $event, $index) { - listViewHelper.selectHandler(item, $index, $scope.items, $scope.selection, $event); - } + } - function selectItem(item, $event, $index) { - listViewHelper.selectHandler(item, $index, vm.itemsWithoutFolders, $scope.selection, $event); - } + function selectContentItem(item, $event, $index) { + listViewHelper.selectHandler(item, $index, $scope.items, $scope.selection, $event); + } - function selectFolder(folder, $event, $index) { - listViewHelper.selectHandler(folder, $index, $scope.folders, $scope.selection, $event); - } + function selectItem(item, $event, $index) { + listViewHelper.selectHandler(item, $index, vm.itemsWithoutFolders, $scope.selection, $event); + } - function goToItem(item, $event, $index) { - $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + item.id); - } + function selectFolder(folder, $event, $index) { + listViewHelper.selectHandler(folder, $index, $scope.folders, $scope.selection, $event); + } - activate(); + function goToItem(item, $event, $index) { + $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + item.id); + } - } + activate(); - angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.GridLayoutController", ListViewGridLayoutController); + } + + angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.GridLayoutController", ListViewGridLayoutController); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js index 15c2042477..799cc5894c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js @@ -1,89 +1,96 @@ (function () { - "use strict"; + "use strict"; - function ListViewListLayoutController($scope, listViewHelper, $location, mediaHelper, mediaTypeHelper) { + function ListViewListLayoutController($scope, listViewHelper, $location, mediaHelper, mediaTypeHelper) { - var vm = this; + var vm = this; + var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; - vm.nodeId = $scope.contentId; - //we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles - vm.acceptedFileTypes = !mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles); - vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; - vm.activeDrag = false; - vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; - vm.acceptedMediatypes = []; + vm.nodeId = $scope.contentId; - vm.selectItem = selectItem; - vm.clickItem = clickItem; - vm.selectAll = selectAll; - vm.isSelectedAll = isSelectedAll; - vm.isSortDirection = isSortDirection; - vm.sort = sort; - vm.dragEnter = dragEnter; - vm.dragLeave = dragLeave; - vm.onFilesQueue = onFilesQueue; - vm.onUploadComplete = onUploadComplete; - - function activate() { - - if ($scope.entityType === 'media') { - mediaTypeHelper.getAllowedImagetypes(vm.nodeId).then(function (types) { - vm.acceptedMediatypes = types; - }); + // Use whitelist of allowed file types if provided + vm.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); + if (vm.acceptedFileTypes === '') { + // If not provided, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles + vm.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles); } - } + vm.maxFileSize = umbracoSettings.maxFileSize + "KB"; + vm.activeDrag = false; + vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; + vm.acceptedMediatypes = []; - function selectAll($event) { - listViewHelper.selectAllItems($scope.items, $scope.selection, $event); - } + vm.selectItem = selectItem; + vm.clickItem = clickItem; + vm.selectAll = selectAll; + vm.isSelectedAll = isSelectedAll; + vm.isSortDirection = isSortDirection; + vm.sort = sort; + vm.dragEnter = dragEnter; + vm.dragLeave = dragLeave; + vm.onFilesQueue = onFilesQueue; + vm.onUploadComplete = onUploadComplete; - function isSelectedAll() { - return listViewHelper.isSelectedAll($scope.items, $scope.selection); - } + function activate() { - function selectItem(selectedItem, $index, $event) { - listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event); - } + if ($scope.entityType === 'media') { + mediaTypeHelper.getAllowedImagetypes(vm.nodeId).then(function (types) { + vm.acceptedMediatypes = types; + }); + } - function clickItem(item) { - // if item.id is 2147483647 (int.MaxValue) use item.key - $location.path($scope.entityType + '/' +$scope.entityType + '/edit/' + (item.id === 2147483647 ? item.key : item.id)); - } + } - function isSortDirection(col, direction) { - return listViewHelper.setSortingDirection(col, direction, $scope.options); - } + function selectAll($event) { + listViewHelper.selectAllItems($scope.items, $scope.selection, $event); + } - function sort(field, allow, isSystem) { - if (allow) { - $scope.options.orderBySystemField = isSystem; - listViewHelper.setSorting(field, allow, $scope.options); + function isSelectedAll() { + return listViewHelper.isSelectedAll($scope.items, $scope.selection); + } + + function selectItem(selectedItem, $index, $event) { + listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event); + } + + function clickItem(item) { + // if item.id is 2147483647 (int.MaxValue) use item.key + $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + (item.id === 2147483647 ? item.key : item.id)); + } + + function isSortDirection(col, direction) { + return listViewHelper.setSortingDirection(col, direction, $scope.options); + } + + function sort(field, allow, isSystem) { + if (allow) { + $scope.options.orderBySystemField = isSystem; + listViewHelper.setSorting(field, allow, $scope.options); + $scope.getContent($scope.contentId); + } + } + + // Dropzone upload functions + function dragEnter(el, event) { + vm.activeDrag = true; + } + + function dragLeave(el, event) { + vm.activeDrag = false; + } + + function onFilesQueue() { + vm.activeDrag = false; + } + + function onUploadComplete() { $scope.getContent($scope.contentId); - } - } + } - // Dropzone upload functions - function dragEnter(el, event) { - vm.activeDrag = true; - } + activate(); - function dragLeave(el, event) { - vm.activeDrag = false; - } + } - function onFilesQueue() { - vm.activeDrag = false; - } + angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.ListLayoutController", ListViewListLayoutController); - function onUploadComplete() { - $scope.getContent($scope.contentId); - } - - activate(); - - } - -angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.ListLayoutController", ListViewListLayoutController); - -}) (); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index dafe6cc3c7..6652c52cc0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -112,10 +112,9 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl // content picker. THen we don't have to worry about setting ids, render models, models, we just set one and let the // watch do all the rest. $timeout(function(){ - angular.forEach($scope.images, function(value, key){ - r.push(value.id); + angular.forEach($scope.images, function(value, key) { + r.push($scope.model.config.idType === "udi" ? value.udi : value.id); }); - $scope.ids = r; $scope.sync(); }, 500, false); diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 1b3a44644b..eba9c4f1f2 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2376,9 +2376,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" True True - 7600 + 7630 / - http://localhost:7600 + http://localhost:7630 False False @@ -2395,7 +2395,26 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" + + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll + + + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v11.0\Web\Microsoft.Web.Publishing.Tasks.dll + + + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v12.0\Web\Microsoft.Web.Publishing.Tasks.dll + + + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v14.0\Web\Microsoft.Web.Publishing.Tasks.dll + + + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v15.0\Web\Microsoft.Web.Publishing.Tasks.dll + + + + + diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index 78d13ab600..f208ab649e 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -102,6 +102,9 @@ ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,svg,php,htaccess + + + Textstring diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 00580ba17e..281dbacdbd 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -150,7 +150,7 @@ Udgivelsesdato Dato for Fortryd udgivelse Fjern dato - Sorteringrækkefølgen er opdateret + Sorteringsrækkefølgen er opdateret For at sortere, træk siderne eller klik på en af kolonnehovederne. Du kan vælge flere sider ved at holde "shift" eller "control" nede mens du vælger. Statistik Titel (valgfri) @@ -184,7 +184,7 @@ Vælg en type og skriv en titel "dokument typer".]]> "media typer".]]> - Dokument type uden skabelon + Dokumenttype uden skabelon Ny mappe Ny datatype @@ -580,13 +580,13 @@ Berørte filer og foldere Flere informationer om at opsætte rettigheder for Umbraco her Du er nødt til at give ASP.NET 'modify' rettigheder på følgende filer/foldere - Dine rettighedsinstillinger er næsten perfekte!

Du kan køre Umbraco uden problemer, men du vil ikke være i stand til at installere pakker, som er anbefalet for at få fuldt udbytte af Umbraco.]]>
+ Dine rettighedsindstillinger er næsten perfekte!

Du kan køre Umbraco uden problemer, men du vil ikke være i stand til at installere pakker, som er anbefalet for at få fuldt udbytte af Umbraco.]]>
Hvorledes besluttes Klik her for at læse tekstversionen video tutorials om at opsætte folderrettigheder for Umbraco eller læs tekstversionen.]]> - Dine rettighedsinstillinger kan være et problem!

Du kan afvikle Umbraco uden problemer, men du vil ikke være i stand til at oprette foldere eller installere pakker, hvilket er anbefalet for at få fuldt udbytte af Umbraco.]]>
- Dine rettighedsinstillinger er ikke klar til Umbraco!

For at afvikle Umbraco er du nødt til at opdatere dine rettighedsinstillinger.]]>
- Dine rettighedsinstillinger er perfekte!

Du er nu parat til at afvikle Umbraco og installere pakker!]]>
+ Dine rettighedsindstillinger kan være et problem!

Du kan afvikle Umbraco uden problemer, men du vil ikke være i stand til at oprette foldere eller installere pakker, hvilket er anbefalet for at få fuldt udbytte af Umbraco.]]>
+ Dine rettighedsindstillinger er ikke klar til Umbraco!

For at afvikle Umbraco er du nødt til at opdatere dine rettighedsindstillinger.]]>
+ Dine rettighedsindstillinger er perfekte!

Du er nu parat til at afvikle Umbraco og installere pakker!]]>
Løser folder problem Følg dette link for mere information om udfordringer med ASP.NET og oprettelse af foldere Sætter folderrettigheder op @@ -598,7 +598,7 @@ Dette er vores liste over anbefalede moduler. Kryds dem af du ønsker at installere eller se den fulde liste af moduler ]]> Kun anbefalet for erfarne brugere Jeg ønsker at begynder med et simpelt website - "Runway" er et simpelt website som stiller nogle basale dokumenttyper og skabeloner til rådighed. Instaleringsprogrammet kan automatisk opsætte Runway for dig, men du kan nemt redigere, udvide eller fjerne det. Det er ikke nødvendigt og du kan sagtens bruge Umbraco uden. Men Runway tilbyder et fundament, som er baseret på 'Best Practices', som får dig igang hurtigere end nogensinde før. Hvis du vælger at installere Runway, kan du efter eget valg vælge de grundlæggende byggesten kaldet 'Runway Modules' til at forbedre dine Runway-sider.

Inkluderet med Runway:Home Page, Getting Started page, Installing Modules page.
Valgfri Moduler: Top Navigation, Sitemap, Contact, Gallery.

]]>
+ "Runway" er et simpelt website som stiller nogle basale dokumenttyper og skabeloner til rådighed. Installeringsprogrammet kan automatisk opsætte Runway for dig, men du kan nemt redigere, udvide eller fjerne det. Det er ikke nødvendigt og du kan sagtens bruge Umbraco uden. Men Runway tilbyder et fundament, som er baseret på 'Best Practices', som får dig igang hurtigere end nogensinde før. Hvis du vælger at installere Runway, kan du efter eget valg vælge de grundlæggende byggesten kaldet 'Runway Modules' til at forbedre dine Runway-sider.

Inkluderet med Runway:Home Page, Getting Started page, Installing Modules page.
Valgfri Moduler: Top Navigation, Sitemap, Contact, Gallery.

]]>
Hvad er Runway Skridt 1/5: Acceptér licens Skridt 2/5: Database-konfiguration @@ -715,7 +715,7 @@ Mange hilsner fra Umbraco robotten Pakke opbevaringsbase Bekræft af-installering Pakken blev fjernet - Pakken er på succefuld vis blevet fjernet + Pakken er på succesfuld vis blevet fjernet Afinstallér pakke @@ -796,7 +796,7 @@ Mange hilsner fra Umbraco robotten Tilføj række Tilføj indhold Slip indhold - Instillinger tilføjet + Indstillinger tilføjet Indholdet er ikke tilladt her Indholdet er tilladt her @@ -941,7 +941,7 @@ Mange hilsner fra Umbraco robotten Faneblad Titel på faneblad Faneblade - Master Dokument Type + Master Dokumenttype Opret matchende skabelon @@ -1025,7 +1025,7 @@ Mange hilsner fra Umbraco robotten Indsæt pladsholder for indholdsområde Indsæt - Hvad vil du indsætte ? + Hvad vil du indsætte? Oversættelse Indsætter en oversætbar tekst, som skifter efter det sprog, som websitet vises i. diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index ecea653650..d99cd8f43f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1370,6 +1370,8 @@ To manage your website, simply open the Umbraco back office and start adding con Templates XSLT Files Analytics + Partial Views + Partial View Macro Files New update ready diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml index 1f538e7fa0..0341316f25 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml @@ -1252,6 +1252,8 @@ 模板 XSLT文件 分析 + 分部视图 + 分部视图宏文件 有可用更新 diff --git a/src/Umbraco.Web.UI/umbraco/translation/default.aspx b/src/Umbraco.Web.UI/umbraco/translation/default.aspx index 5884054a2f..06a5c2c9e6 100644 --- a/src/Umbraco.Web.UI/umbraco/translation/default.aspx +++ b/src/Umbraco.Web.UI/umbraco/translation/default.aspx @@ -16,7 +16,7 @@

- When you have completed the translation. Upload the edited XML file here. The related translation tasks will automaticly be closed when a file is uploaded. + When you have completed the translation. Upload the edited XML file here. The related translation tasks will automatically be closed when a file is uploaded.

diff --git a/src/Umbraco.Web.UI/umbraco/users/PermissionEditor.aspx b/src/Umbraco.Web.UI/umbraco/users/PermissionEditor.aspx index d80a60c92c..75b5d6e66a 100644 --- a/src/Umbraco.Web.UI/umbraco/users/PermissionEditor.aspx +++ b/src/Umbraco.Web.UI/umbraco/users/PermissionEditor.aspx @@ -1,5 +1,5 @@ <%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="../masterpages/umbracoPage.Master" CodeBehind="PermissionEditor.aspx.cs" Inherits="umbraco.cms.presentation.user.PermissionEditor" %> - +<%@ Import Namespace="Umbraco.Web" %> <%@ Register Src="../controls/Tree/TreeControl.ascx" TagName="TreeControl" TagPrefix="umbraco" %> <%@ Register Src="NodePermissions.ascx" TagName="NodePermissions" TagPrefix="user" %> <%@ Register TagPrefix="ui" Namespace="umbraco.uicontrols" Assembly="controls" %> diff --git a/src/Umbraco.Web.UI/umbraco_client/Editors/EditXslt.js b/src/Umbraco.Web.UI/umbraco_client/Editors/EditXslt.js index d17296a958..67f0259afb 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Editors/EditXslt.js +++ b/src/Umbraco.Web.UI/umbraco_client/Editors/EditXslt.js @@ -37,7 +37,7 @@ codeVal = UmbEditor.GetCode(); } umbraco.presentation.webservices.codeEditorSave.SaveXslt( - fileName, self._opts.originalFileName, codeVal, self._opts.skipTestingCheckBox.is(':checked'), + fileName, self._opts.originalFileName, codeVal, true, function (t) { self.submitSucces(t); }, function (t) { self.submitFailure(t); }); diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 6ee7d3bfba..3a484944e6 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -433,6 +433,10 @@ + + + + diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 8b58ec92ed..df25d9dfe0 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using Umbraco.Core; using Umbraco.Core.Events; using Umbraco.Core.Models; @@ -11,9 +12,14 @@ using umbraco.BusinessLogic; using umbraco.cms.businesslogic; using System.Linq; using System.Reflection; +using System.Web; +using System.Web.Hosting; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Publishing; +using Umbraco.Web.Routing; +using Umbraco.Web.Security; using Content = Umbraco.Core.Models.Content; using ApplicationTree = Umbraco.Core.Models.ApplicationTree; using DeleteEventArgs = umbraco.cms.businesslogic.DeleteEventArgs; @@ -787,14 +793,39 @@ namespace Umbraco.Web.Cache ///
/// internal static void HandleEvents(IEnumerable events) - { - foreach (var e in events) + { + //TODO: We should remove this in v8, this is a backwards compat hack and is needed because when we are using Deploy, the events will be raised on a background + //thread which means that cache refreshers will also execute on a background thread and in many cases developers may be using UmbracoContext.Current in their + //cache refresher handlers, so before we execute all of the events, we'll ensure a context + UmbracoContext tempContext = null; + if (UmbracoContext.Current == null) { - var handler = FindHandler(e); - if (handler == null) continue; - - handler.Invoke(null, new[] { e.Sender, e.Args }); + var httpContext = new HttpContextWrapper(HttpContext.Current ?? new HttpContext(new SimpleWorkerRequest("temp.aspx", "", new StringWriter()))); + tempContext = UmbracoContext.EnsureContext( + httpContext, + ApplicationContext.Current, + new WebSecurity(httpContext, ApplicationContext.Current), + UmbracoConfig.For.UmbracoSettings(), + UrlProviderResolver.Current.Providers, + true); } + + try + { + foreach (var e in events) + { + var handler = FindHandler(e); + if (handler == null) continue; + + handler.Invoke(null, new[] { e.Sender, e.Args }); + } + } + finally + { + if (tempContext != null) + tempContext.Dispose(); + } + } /// diff --git a/src/Umbraco.Web/Cache/PageCacheRefresher.cs b/src/Umbraco.Web/Cache/PageCacheRefresher.cs index 83d38924ed..e884c9b3b8 100644 --- a/src/Umbraco.Web/Cache/PageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/PageCacheRefresher.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.Cache /// /// If Load balancing is enabled (by default disabled, is set in umbracoSettings.config) PageCacheRefresher will be called /// everytime content is added/updated/removed to ensure that the content cache is identical on all load balanced servers - /// + /// public class PageCacheRefresher : TypedCacheRefresherBase { @@ -51,6 +51,7 @@ namespace Umbraco.Web.Cache public override void RefreshAll() { content.Instance.RefreshContentFromDatabase(); + XmlPublishedContent.ClearRequest(); base.RefreshAll(); } @@ -62,6 +63,7 @@ namespace Umbraco.Web.Cache { ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); content.Instance.UpdateDocumentCache(id); + XmlPublishedContent.ClearRequest(); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); base.Refresh(id); @@ -75,6 +77,7 @@ namespace Umbraco.Web.Cache { ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); content.Instance.ClearDocumentCache(id, false); + XmlPublishedContent.ClearRequest(); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); ClearAllIsolatedCacheByEntityType(); diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index ff4c3281b2..52ec819ec2 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -211,7 +211,7 @@ namespace Umbraco.Web.Editors if (identityUser != null) { var user = Services.UserService.GetByEmail(model.Email); - if (user != null && user.IsLockedOut == false) + if (user != null) { var code = await UserManager.GeneratePasswordResetTokenAsync(identityUser.Id); var callbackUrl = ConstructCallbackUrl(identityUser.Id, code); @@ -310,9 +310,31 @@ namespace Umbraco.Web.Editors var result = await UserManager.ResetPasswordAsync(model.UserId, model.ResetCode, model.Password); if (result.Succeeded) { + var lockedOut = await UserManager.IsLockedOutAsync(model.UserId); + if (lockedOut) + { + Logger.Info( + "User {0} is currently locked out, unlocking and resetting AccessFailedCount", + () => model.UserId); + + //var user = await UserManager.FindByIdAsync(model.UserId); + var unlockResult = await UserManager.SetLockoutEndDateAsync(model.UserId, DateTimeOffset.Now); + if(unlockResult.Succeeded == false) + { + Logger.Warn("Could not unlock for user {0} - error {1}", + () => model.UserId, () => unlockResult.Errors.First()); + } + + var resetAccessFailedCountResult = await UserManager.ResetAccessFailedCountAsync(model.UserId); + if (resetAccessFailedCountResult.Succeeded == false) + { + Logger.Warn("Could not reset access failed count {0} - error {1}", + () => model.UserId, () => unlockResult.Errors.First()); + } + } + return Request.CreateResponse(HttpStatusCode.OK); } - return Request.CreateValidationErrorResponse( result.Errors.Any() ? result.Errors.First() : "Set password failed"); } diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 901189e28a..a72b9ac85e 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -391,6 +391,10 @@ namespace Umbraco.Web.Editors { "disallowedUploadFiles", string.Join(",", UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles) + }, + { + "allowedUploadFiles", + string.Join(",", UmbracoConfig.For.UmbracoSettings().Content.AllowedUploadFiles) }, { "maxFileSize", diff --git a/src/Umbraco.Web/Editors/CodeFileController.cs b/src/Umbraco.Web/Editors/CodeFileController.cs index 5f9f719a69..a7f420fc3b 100644 --- a/src/Umbraco.Web/Editors/CodeFileController.cs +++ b/src/Umbraco.Web/Editors/CodeFileController.cs @@ -42,13 +42,13 @@ namespace Umbraco.Web.Editors switch (type) { case Core.Constants.Trees.PartialViews: - var view = new PartialView(display.VirtualPath); + var view = new PartialView(PartialViewType.PartialView, display.VirtualPath); view.Content = display.Content; var result = Services.FileService.CreatePartialView(view, display.Snippet, Security.CurrentUser.Id); return result.Success == true ? Request.CreateResponse(HttpStatusCode.OK) : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); case Core.Constants.Trees.PartialViewMacros: - var viewMacro = new PartialView(display.VirtualPath); + var viewMacro = new PartialView(PartialViewType.PartialViewMacro, display.VirtualPath); viewMacro.Content = display.Content; var resultMacro = Services.FileService.CreatePartialViewMacro(viewMacro, display.Snippet, Security.CurrentUser.Id); return resultMacro.Success == true ? Request.CreateResponse(HttpStatusCode.OK) : Request.CreateNotificationValidationErrorResponse(resultMacro.Exception.Message); @@ -219,13 +219,13 @@ namespace Umbraco.Web.Editors switch (type) { case Core.Constants.Trees.PartialViews: - codeFileDisplay = Mapper.Map(new PartialView(string.Empty)); + codeFileDisplay = Mapper.Map(new PartialView(PartialViewType.PartialView, string.Empty)); codeFileDisplay.VirtualPath = SystemDirectories.PartialViews; if (snippetName.IsNullOrWhiteSpace() == false) codeFileDisplay.Content = Services.FileService.GetPartialViewSnippetContent(snippetName); break; case Core.Constants.Trees.PartialViewMacros: - codeFileDisplay = Mapper.Map(new PartialView(string.Empty)); + codeFileDisplay = Mapper.Map(new PartialView(PartialViewType.PartialViewMacro, string.Empty)); codeFileDisplay.VirtualPath = SystemDirectories.MacroPartials; if (snippetName.IsNullOrWhiteSpace() == false) codeFileDisplay.Content = Services.FileService.GetPartialViewMacroSnippetContent(snippetName); @@ -475,7 +475,7 @@ namespace Umbraco.Web.Editors } else { - view = new PartialView(virtualPath + display.Name); + view = new PartialView(PartialViewType.PartialView, virtualPath + display.Name); view.Content = display.Content; partialViewResult = createView(view, display.Snippet, Security.CurrentUser.Id); } diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index 6b8d570e5f..f67ce13950 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -57,7 +57,7 @@ namespace Umbraco.Web.Editors where TPersisted : IContentBase { //Don't update the name if it is empty - if (!contentItem.Name.IsNullOrWhiteSpace()) + if (contentItem.Name.IsNullOrWhiteSpace() == false) { contentItem.PersistedContent.Name = contentItem.Name; } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 4980605374..3ca4195a26 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -33,7 +33,8 @@ using Umbraco.Core.Configuration; using Umbraco.Web.UI; using Notification = Umbraco.Web.Models.ContentEditing.Notification; using Umbraco.Core.Persistence; - +using Umbraco.Core.Configuration.UmbracoSettings; + namespace Umbraco.Web.Editors { /// @@ -723,7 +724,7 @@ namespace Umbraco.Web.Editors var safeFileName = fileName.ToSafeFileName(); var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); - if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false) + if (UmbracoConfig.For.UmbracoSettings().Content.IsFileAllowedForUpload(ext)) { var mediaType = Constants.Conventions.MediaTypes.File; diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index cd004fe926..d7351b0e97 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Text; using Umbraco.Core.Models; using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi.Filters; using Umbraco.Web.WebApi; using System; using System.Diagnostics; @@ -13,8 +12,6 @@ using Umbraco.Core.Services; namespace Umbraco.Web.Editors { - - /// /// The API controller used for building content queries within the template /// @@ -26,10 +23,9 @@ namespace Umbraco.Web.Editors { } public TemplateQueryController(UmbracoContext umbracoContext) - :base(umbracoContext) + : base(umbracoContext) { } - private IEnumerable Terms { get @@ -77,20 +73,19 @@ namespace Umbraco.Web.Editors var sb = new StringBuilder(); var indention = Environment.NewLine + "\t\t\t\t\t\t"; - + sb.Append("Model.Content.Site()"); var timer = new Stopwatch(); - + timer.Start(); var currentPage = umbraco.TypedContentAtRoot().FirstOrDefault(); timer.Stop(); - var pointerNode = currentPage; // adjust the "FROM" - if (model != null && model.Source.Id > 0) + if (model != null && model.Source != null && model.Source.Id > 0) { var targetNode = umbraco.TypedContent(model.Source.Id); @@ -120,10 +115,10 @@ namespace Umbraco.Web.Editors } } } - - // TYPE to return if filtered by type + + // TYPE to return if filtered by type IEnumerable contents; - if (model != null && string.IsNullOrEmpty(model.ContentType.Alias) == false) + if (model != null && model.ContentType != null && string.IsNullOrEmpty(model.ContentType.Alias) == false) { timer.Start(); @@ -154,13 +149,13 @@ namespace Umbraco.Web.Editors foreach (var condition in model.Filters) { - if(string.IsNullOrEmpty( condition.ConstraintValue)) continue; - + if (string.IsNullOrEmpty(condition.ConstraintValue)) continue; + //x is passed in as the parameter alias for the linq where statement clause var operation = condition.BuildCondition("x"); var tokenizedOperation = condition.BuildTokenizedCondition(token); - clause = string.IsNullOrEmpty(clause) ? operation : string.Concat(new[] { clause, " && ", operation }); + clause = string.IsNullOrEmpty(clause) ? operation : string.Concat(new[] { clause, " && ", operation }); tokenizedClause = string.IsNullOrEmpty(tokenizedClause) ? tokenizedOperation : string.Concat(new[] { tokenizedClause, " && ", tokenizedOperation }); token++; @@ -168,7 +163,6 @@ namespace Umbraco.Web.Editors if (string.IsNullOrEmpty(clause) == false) { - timer.Start(); //trial-run the tokenized clause to time the execution @@ -178,14 +172,13 @@ namespace Umbraco.Web.Editors timer.Stop(); - + //the query to output to the editor sb.Append(indention); sb.Append(".Where(x => x.IsVisible())"); sb.Append(indention); sb.AppendFormat(".Where(x => {0})", clause); - } else { @@ -197,7 +190,6 @@ namespace Umbraco.Web.Editors sb.Append(indention); sb.Append(".Where(x => x.IsVisible())"); - } if (model.Sort != null && string.IsNullOrEmpty(model.Sort.Property.Alias) == false) @@ -231,20 +223,19 @@ namespace Umbraco.Web.Editors queryResult.ExecutionTime = timer.ElapsedMilliseconds; queryResult.ResultCount = contents.Count(); queryResult.SampleResults = contents.Take(20).Select(x => new TemplateQueryResult() - { - Icon = "icon-file", - Name = x.Name - }); + { + Icon = "icon-file", + Name = x.Name + }); - - return queryResult; + return queryResult; } private object GetConstraintValue(QueryCondition condition) { switch (condition.Property.Type) { - case "int" : + case "int": return int.Parse(condition.ConstraintValue); case "datetime": DateTime dt; @@ -254,42 +245,41 @@ namespace Umbraco.Web.Editors } } - private IEnumerable SortByDefaultPropertyValue(IEnumerable contents, SortExpression sortExpression) + private IEnumerable SortByDefaultPropertyValue(IEnumerable contents, SortExpression sortExpression) { switch (sortExpression.Property.Alias) { - case "id" : + case "id": return sortExpression.Direction == "ascending" - ? contents.OrderBy(x => x.Id) - : contents.OrderByDescending(x => x.Id); - case "createDate" : - + ? contents.OrderBy(x => x.Id) + : contents.OrderByDescending(x => x.Id); + case "createDate": + return sortExpression.Direction == "ascending" - ? contents.OrderBy(x => x.CreateDate) - : contents.OrderByDescending(x => x.CreateDate); + ? contents.OrderBy(x => x.CreateDate) + : contents.OrderByDescending(x => x.CreateDate); case "publishDate": - + return sortExpression.Direction == "ascending" - ? contents.OrderBy(x => x.UpdateDate) - : contents.OrderByDescending(x => x.UpdateDate); + ? contents.OrderBy(x => x.UpdateDate) + : contents.OrderByDescending(x => x.UpdateDate); case "name": return sortExpression.Direction == "ascending" - ? contents.OrderBy(x => x.Name) - : contents.OrderByDescending(x => x.Name); - default : + ? contents.OrderBy(x => x.Name) + : contents.OrderByDescending(x => x.Name); + default: return sortExpression.Direction == "ascending" - ? contents.OrderBy(x => x.Name) - : contents.OrderByDescending(x => x.Name); - + ? contents.OrderBy(x => x.Name) + : contents.OrderByDescending(x => x.Name); } } - + private IEnumerable GetChildContentTypeAliases(IPublishedContent targetNode, IPublishedContent current) { var aliases = new List(); - - if (targetNode.Id == current.Id) return aliases; + + if (targetNode == null || targetNode.Id == current.Id) return aliases; if (targetNode.Id != current.Id) { aliases.Add(targetNode.DocumentTypeAlias); @@ -309,7 +299,7 @@ namespace Umbraco.Web.Editors { var contentTypes = ApplicationContext.Services.ContentTypeService.GetAllContentTypes() - .Select(x => new ContentTypeModel() { Alias = x.Alias, Name = Services.TextService.Localize("template/contentOfType", tokens: new string[] { x.Name } ) }) + .Select(x => new ContentTypeModel() { Alias = x.Alias, Name = Services.TextService.Localize("template/contentOfType", tokens: new string[] { x.Name }) }) .OrderBy(x => x.Name).ToList(); contentTypes.Insert(0, new ContentTypeModel() { Alias = string.Empty, Name = Services.TextService.Localize("template/allContent") }); @@ -332,7 +322,5 @@ namespace Umbraco.Web.Editors { return Terms; } - - } } \ No newline at end of file diff --git a/src/Umbraco.Web/Extensions/UdiExtensions.cs b/src/Umbraco.Web/Extensions/UdiExtensions.cs index c814d63b8c..3302f5538a 100644 --- a/src/Umbraco.Web/Extensions/UdiExtensions.cs +++ b/src/Umbraco.Web/Extensions/UdiExtensions.cs @@ -1,23 +1,22 @@ -using Umbraco.Core; +using System; +using System.ComponentModel; +using Umbraco.Core; using Umbraco.Core.Models; namespace Umbraco.Web.Extensions { + [Obsolete("Use methods on UmbracoHelper instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static class UdiExtensions { - /// - /// An extension method to easily acquire a typed version of content, media or member item for a given Udi - /// - /// - /// An item if the item is a Document, Media or Member + [Obsolete("Use methods on UmbracoHelper instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IPublishedContent ToPublishedContent(this Udi udi) { - Udi identifier; - if (Udi.TryParse(udi.ToString(), out identifier) == false) - return null; + var guidUdi = udi as GuidUdi; + if (guidUdi == null) return null; - var guidUdi = GuidUdi.Parse(udi.ToString()); - var umbracoType = Constants.UdiEntityType.ToUmbracoObjectType(identifier.EntityType); + var umbracoType = Constants.UdiEntityType.ToUmbracoObjectType(guidUdi.EntityType); var umbracoHelper = new UmbracoHelper(UmbracoContext.Current); var entityService = ApplicationContext.Current.Services.EntityService; @@ -36,7 +35,7 @@ namespace Umbraco.Web.Extensions return umbracoHelper.TypedMember(memberAttempt.Result); break; } - + return null; } } diff --git a/src/Umbraco.Web/ITypedPublishedContentQuery.cs b/src/Umbraco.Web/ITypedPublishedContentQuery.cs index 7a2be964f7..893c036958 100644 --- a/src/Umbraco.Web/ITypedPublishedContentQuery.cs +++ b/src/Umbraco.Web/ITypedPublishedContentQuery.cs @@ -11,8 +11,20 @@ namespace Umbraco.Web /// public interface ITypedPublishedContentQuery { + /// + /// Gets a content item from the cache + /// + /// + /// IPublishedContent TypedContent(int id); + + /// + /// Gets a content item from the cache + /// + /// + /// IPublishedContent TypedContent(Guid id); + IPublishedContent TypedContentSingleAtXPath(string xpath, params XPathVariable[] vars); IEnumerable TypedContent(IEnumerable ids); IEnumerable TypedContent(IEnumerable ids); @@ -20,8 +32,8 @@ namespace Umbraco.Web IEnumerable TypedContentAtXPath(XPathExpression xpath, params XPathVariable[] vars); IEnumerable TypedContentAtRoot(); - // note: we CANNOT implement TypedMedia by Guid in v7 without break-changing IPublishedCache, - // since we don't support XPath navigation of the media tree. + // TODO: we CANNOT implement TypedMedia by Guid in v7 without break-changing IPublishedCache, since we don't support XPath navigation of the media tree. + // surely there is a way we can support this without XPath, it's needed so we can query properly by UDI IPublishedContent TypedMedia(int id); //IPublishedContent TypedMedia(Guid id); diff --git a/src/Umbraco.Web/Models/Mapping/CodeFileDisplayMapper.cs b/src/Umbraco.Web/Models/Mapping/CodeFileDisplayMapper.cs index aa033c91b0..dec0930c07 100644 --- a/src/Umbraco.Web/Models/Mapping/CodeFileDisplayMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/CodeFileDisplayMapper.cs @@ -28,6 +28,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.Snippet, exp => exp.Ignore()); config.CreateMap() + .ForMember(x => x.DeletedDate, exp => exp.Ignore()) .ForMember(x => x.Id, exp => exp.Ignore()) .ForMember(x => x.Key, exp => exp.Ignore()) .ForMember(x => x.Path, exp => exp.Ignore()) @@ -40,6 +41,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.HasIdentity, exp => exp.Ignore()); config.CreateMap() + .ForMember(x => x.DeletedDate, exp => exp.Ignore()) .ForMember(x => x.Id, exp => exp.Ignore()) .ForMember(x => x.Key, exp => exp.Ignore()) .ForMember(x => x.Path, exp => exp.Ignore()) diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs index de5b5a14fd..24620509e9 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs @@ -48,6 +48,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(type => type.Key, expression => expression.Ignore()) .ForMember(type => type.CreateDate, expression => expression.Ignore()) .ForMember(type => type.UpdateDate, expression => expression.Ignore()) + .ForMember(type => type.DeletedDate, expression => expression.Ignore()) .ForMember(type => type.HasIdentity, expression => expression.Ignore()); config.CreateMap() @@ -72,7 +73,7 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() //do the base mapping .MapBaseContentTypeSaveToEntity(applicationContext) - .ConstructUsing((source) => new MediaType(source.ParentId)) + .ConstructUsing((source) => new MediaType(source.ParentId)) .AfterMap((source, dest) => { ContentTypeModelMapperExtensions.AfterMapMediaTypeSaveToEntity(source, dest, applicationContext); diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs index cd42c87a56..274771fead 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs @@ -31,6 +31,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.HasIdentity, map => map.Ignore()) .ForMember(dest => dest.CreateDate, map => map.Ignore()) .ForMember(dest => dest.UpdateDate, map => map.Ignore()) + .ForMember(dest => dest.DeletedDate, map => map.Ignore()) .ForMember(dest => dest.PropertyTypes, map => map.Ignore()); } @@ -175,6 +176,7 @@ namespace Umbraco.Web.Models.Mapping //These get persisted as part of the saving procedure, nothing to do with the display model .ForMember(dto => dto.CreateDate, expression => expression.Ignore()) .ForMember(dto => dto.UpdateDate, expression => expression.Ignore()) + .ForMember(dto => dto.DeletedDate, expression => expression.Ignore()) .ForMember(dto => dto.AllowedAsRoot, expression => expression.MapFrom(display => display.AllowAsRoot)) .ForMember(dto => dto.CreatorId, expression => expression.Ignore()) diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs index e78aeaf6a3..7e9a00f760 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs @@ -108,6 +108,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.Level, expression => expression.Ignore()) .ForMember(x => x.SortOrder, expression => expression.Ignore()) .ForMember(x => x.CreateDate, expression => expression.Ignore()) + .ForMember(x => x.DeletedDate, expression => expression.Ignore()) .ForMember(x => x.UpdateDate, expression => expression.Ignore()); //Converts a property editor to a new list of pre-value fields - used when creating a new data type or changing a data type with new pre-vals diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index e0eb318866..976026bd97 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -55,8 +55,9 @@ namespace Umbraco.Web.Models.Mapping .ForMember(member => member.SortOrder, expression => expression.Ignore()) .ForMember(member => member.AdditionalData, expression => expression.Ignore()) .ForMember(member => member.FailedPasswordAttempts, expression => expression.Ignore()) - //TODO: Support these eventually - .ForMember(member => member.PasswordQuestion, expression => expression.Ignore()) + .ForMember(member => member.DeletedDate, expression => expression.Ignore()) + //TODO: Support these eventually + .ForMember(member => member.PasswordQuestion, expression => expression.Ignore()) .ForMember(member => member.RawPasswordAnswerValue, expression => expression.Ignore()); //FROM IMember TO MediaItemDisplay diff --git a/src/Umbraco.Web/Models/Mapping/TemplateModelMapper.cs b/src/Umbraco.Web/Models/Mapping/TemplateModelMapper.cs index d673e573a8..19677fccc8 100644 --- a/src/Umbraco.Web/Models/Mapping/TemplateModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/TemplateModelMapper.cs @@ -14,6 +14,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.Notifications, exp => exp.Ignore()); config.CreateMap() + .ForMember(x => x.DeletedDate, exp => exp.Ignore()) .ForMember(x => x.Key, exp => exp.Ignore()) .ForMember(x => x.Path, exp => exp.Ignore()) .ForMember(x => x.CreateDate, exp => exp.Ignore()) diff --git a/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs b/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs index cba92eeff7..7e30e0881b 100644 --- a/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs +++ b/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs @@ -1,8 +1,10 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Web.Models { + [Serializable] [DataContract(Name = "validatePasswordReset", Namespace = "")] public class ValidatePasswordResetCodeModel { diff --git a/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs b/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs index 96a1211589..8a96c7ac3e 100644 --- a/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs +++ b/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs @@ -9,7 +9,8 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using umbraco; - +using Umbraco.Core.Configuration.UmbracoSettings; + namespace Umbraco.Web.PropertyEditors { internal class UploadFileTypeValidator : IPropertyValidator @@ -41,8 +42,9 @@ namespace Umbraco.Web.PropertyEditors internal static bool ValidateFileExtension(string fileName) { if (fileName.IndexOf('.') <= 0) return false; - var extension = Path.GetExtension(fileName).TrimStart("."); - return UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Any(x => StringExtensions.InvariantEquals(x, extension)) == false; + var extension = Path.GetExtension(fileName).TrimStart("."); + + return UmbracoConfig.For.UmbracoSettings().Content.IsFileAllowedForUpload(extension); } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerPropertyConverter.cs index ca147698ec..9876868af1 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerPropertyConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerPropertyConverter.cs @@ -50,10 +50,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters /// public override bool IsConverter(PublishedPropertyType propertyType) { + if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.ContentPicker2Alias)) + return true; + if (UmbracoConfig.For.UmbracoSettings().Content.EnablePropertyValueConverters) { - return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.ContentPickerAlias) - || propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.ContentPicker2Alias); + return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.ContentPickerAlias); } return false; } @@ -106,25 +108,26 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return null; } - if (UmbracoContext.Current != null) + if (UmbracoContext.Current == null) return source; + + if ((propertyType.PropertyTypeAlias != null && PropertiesToExclude.Contains(propertyType.PropertyTypeAlias.ToLower(CultureInfo.InvariantCulture))) == false) { - if ((propertyType.PropertyTypeAlias != null && PropertiesToExclude.Contains(propertyType.PropertyTypeAlias.ToLower(CultureInfo.InvariantCulture))) == false) + IPublishedContent content; + if (source is int) { - IPublishedContent content; - if (source is int) - { - var sourceInt = (int)source; - content = UmbracoContext.Current.ContentCache.GetById(sourceInt); - if(content != null) - return content; - } - else - { - var sourceUdi = source as Udi; - content = sourceUdi.ToPublishedContent(); - if (content != null) - return content; - } + var sourceInt = (int)source; + content = UmbracoContext.Current.ContentCache.GetById(sourceInt); + if(content != null) + return content; + } + else + { + var sourceUdi = source as Udi; + if (sourceUdi == null) return null; + var umbHelper = new UmbracoHelper(UmbracoContext.Current); + content = umbHelper.TypedContent(sourceUdi); + if (content != null) + return content; } } return source; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/LegacyRelatedLinksEditorValueConvertor.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/LegacyRelatedLinksEditorValueConvertor.cs index ee64bf6c15..df4a4356b6 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/LegacyRelatedLinksEditorValueConvertor.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/LegacyRelatedLinksEditorValueConvertor.cs @@ -18,16 +18,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)] public class LegacyRelatedLinksEditorValueConvertor : PropertyValueConverterBase { - private static readonly string[] MatchingEditors = { - Constants.PropertyEditors.RelatedLinksAlias, - Constants.PropertyEditors.RelatedLinks2Alias - }; public override bool IsConverter(PublishedPropertyType propertyType) { if (UmbracoConfig.For.UmbracoSettings().Content.EnablePropertyValueConverters == false) { - return MatchingEditors.Contains(propertyType.PropertyEditorAlias); + return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.RelatedLinksAlias); } return false; } @@ -42,37 +38,38 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters try { var obj = JsonConvert.DeserializeObject(sourceString); + //update the internal links if we have a context - if (UmbracoContext.Current != null) + if (UmbracoContext.Current == null) return obj; + + var helper = new UmbracoHelper(UmbracoContext.Current); + foreach (var a in obj) { - var helper = new UmbracoHelper(UmbracoContext.Current); - foreach (var a in obj) + var type = a.Value("type"); + if (type.IsNullOrWhiteSpace() == false) { - var type = a.Value("type"); - if (type.IsNullOrWhiteSpace() == false) + if (type == "internal") { - if (type == "internal") + switch (propertyType.PropertyEditorAlias) { - switch (propertyType.PropertyEditorAlias) - { - case Constants.PropertyEditors.RelatedLinksAlias: - var intLinkId = a.Value("link"); - var intLink = helper.NiceUrl(intLinkId); - a["link"] = intLink; - break; - case Constants.PropertyEditors.RelatedLinks2Alias: - var strLinkId = a.Value("link"); - var udiAttempt = strLinkId.TryConvertTo(); - if (udiAttempt) - { - var content = udiAttempt.Result.ToPublishedContent(); - a["link"] = helper.NiceUrl(content.Id); - } - break; - } - } + case Constants.PropertyEditors.RelatedLinksAlias: + var intLinkId = a.Value("link"); + var intLink = helper.NiceUrl(intLinkId); + a["link"] = intLink; + break; + case Constants.PropertyEditors.RelatedLinks2Alias: + var strLinkId = a.Value("link"); + var udiAttempt = strLinkId.TryConvertTo(); + if (udiAttempt) + { + var content = helper.TypedContent(udiAttempt.Result); + if (content == null) break; + a["link"] = helper.NiceUrl(content.Id); + } + break; + } } - } + } } return obj; } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerPropertyConverter.cs index 4b3db67025..5f54363975 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerPropertyConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerPropertyConverter.cs @@ -12,6 +12,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; @@ -122,7 +123,14 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters /// public override bool IsConverter(PublishedPropertyType propertyType) { - return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MediaPicker2Alias); + if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MediaPicker2Alias)) + return true; + + if (UmbracoConfig.For.UmbracoSettings().Content.EnablePropertyValueConverters) + { + return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MediaPickerAlias); + } + return false; } /// @@ -173,11 +181,13 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var udis = (Udi[])source; var mediaItems = new List(); + if (UmbracoContext.Current == null) return source; + var helper = new UmbracoHelper(UmbracoContext.Current); if (udis.Any()) { foreach (var udi in udis) { - var item = udi.ToPublishedContent(); + var item = helper.TypedMedia(udi); if (item != null) mediaItems.Add(item); } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerPropertyConverter.cs index 4467c464f5..e89a5c1a09 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerPropertyConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerPropertyConverter.cs @@ -17,10 +17,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { public override bool IsConverter(PublishedPropertyType propertyType) { + if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MemberPicker2Alias)) + return true; + if (UmbracoConfig.For.UmbracoSettings().Content.EnablePropertyValueConverters) { - return propertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.MemberPickerAlias) - || propertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.MemberPicker2Alias); + return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MemberPickerAlias); } return false; } @@ -41,23 +43,22 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters if (source == null) return null; - if (UmbracoContext.Current != null) + if (UmbracoContext.Current == null) return source; + + var umbracoHelper = new UmbracoHelper(UmbracoContext.Current); + IPublishedContent member; + if (source is int) + { + member = umbracoHelper.TypedMember((int)source); + if (member != null) + return member; + } + else { - IPublishedContent member; - if (source is int) - { - var membershipHelper = new MembershipHelper(UmbracoContext.Current); - member = membershipHelper.GetById((int)source); - if (member != null) - return member; - } - else - { - var sourceUdi = source as Udi; - member = sourceUdi.ToPublishedContent(); - if (member != null) - return member; - } + var sourceUdi = source as Udi; + member = umbracoHelper.TypedMember(sourceUdi); + if (member != null) + return member; } return source; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs index c0c0321e91..249b7e0cac 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs @@ -52,10 +52,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters /// public override bool IsConverter(PublishedPropertyType propertyType) { + if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePicker2Alias)) + return true; + if (UmbracoConfig.For.UmbracoSettings().Content.EnablePropertyValueConverters) { - return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePickerAlias) - || propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePicker2Alias); + return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePickerAlias); } return false; } @@ -119,68 +121,67 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters } //TODO: Inject an UmbracoHelper and create a GetUmbracoHelper method based on either injected or singleton - if (UmbracoContext.Current != null) + if (UmbracoContext.Current == null) return source; + + var umbHelper = new UmbracoHelper(UmbracoContext.Current); + + if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePickerAlias)) { - if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePickerAlias)) + var nodeIds = (int[])source; + + if ((propertyType.PropertyTypeAlias != null && PropertiesToExclude.InvariantContains(propertyType.PropertyTypeAlias)) == false) { - var nodeIds = (int[])source; + var multiNodeTreePicker = new List(); - if ((propertyType.PropertyTypeAlias != null && PropertiesToExclude.InvariantContains(propertyType.PropertyTypeAlias)) == false) + var objectType = UmbracoObjectTypes.Unknown; + + foreach (var nodeId in nodeIds) { - var multiNodeTreePicker = new List(); + var multiNodeTreePickerItem = + GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Document, umbHelper.TypedContent) + ?? GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Media, umbHelper.TypedMedia) + ?? GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Member, umbHelper.TypedMember); - if (nodeIds.Length > 0) + if (multiNodeTreePickerItem != null) { - var umbHelper = new UmbracoHelper(UmbracoContext.Current); - var objectType = UmbracoObjectTypes.Unknown; - - foreach (var nodeId in nodeIds) - { - var multiNodeTreePickerItem = - GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Document, umbHelper.TypedContent) - ?? GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Media, umbHelper.TypedMedia) - ?? GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Member, umbHelper.TypedMember); - - if (multiNodeTreePickerItem != null) - { - multiNodeTreePicker.Add(multiNodeTreePickerItem); - } - } + multiNodeTreePicker.Add(multiNodeTreePickerItem); } - - return multiNodeTreePicker; } - // return the first nodeId as this is one of the excluded properties that expects a single id - return nodeIds.FirstOrDefault(); + return multiNodeTreePicker; } - if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePicker2Alias)) + // return the first nodeId as this is one of the excluded properties that expects a single id + return nodeIds.FirstOrDefault(); + } + + if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePicker2Alias)) + { + var udis = (Udi[])source; + + if ((propertyType.PropertyTypeAlias != null && PropertiesToExclude.InvariantContains(propertyType.PropertyTypeAlias)) == false) { - var udis = (Udi[])source; + var multiNodeTreePicker = new List(); - if ((propertyType.PropertyTypeAlias != null && PropertiesToExclude.InvariantContains(propertyType.PropertyTypeAlias)) == false) + var objectType = UmbracoObjectTypes.Unknown; + + foreach (var udi in udis) { - var multiNodeTreePicker = new List(); - - if (udis.Length > 0) + var multiNodeTreePickerItem = + GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Document, umbHelper.TypedContent) + ?? GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Media, umbHelper.TypedMedia) + ?? GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Member, umbHelper.TypedMember); + if (multiNodeTreePickerItem != null) { - foreach (var udi in udis) - { - var item = udi.ToPublishedContent(); - if (item != null) - { - multiNodeTreePicker.Add(item); - } - } + multiNodeTreePicker.Add(multiNodeTreePickerItem); } - - return multiNodeTreePicker; } - // return the first nodeId as this is one of the excluded properties that expects a single id - return udis.FirstOrDefault(); + return multiNodeTreePicker; } + + // return the first nodeId as this is one of the excluded properties that expects a single id + return udis.FirstOrDefault(); } return source; } @@ -193,7 +194,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters /// The type of content expected/supported by /// A function to fetch content of type /// The requested content, or null if either it does not exist or does not match - private IPublishedContent GetPublishedContent(int nodeId, ref UmbracoObjectTypes actualType, UmbracoObjectTypes expectedType, Func contentFetcher) + private IPublishedContent GetPublishedContent(T nodeId, ref UmbracoObjectTypes actualType, UmbracoObjectTypes expectedType, Func contentFetcher) { // is the actual type supported by the content fetcher? if (actualType != UmbracoObjectTypes.Unknown && actualType != expectedType) diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs index b95644d9ca..67ff40fed4 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs @@ -53,10 +53,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters /// public override bool IsConverter(PublishedPropertyType propertyType) { + if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.RelatedLinks2Alias)) + return true; + if (UmbracoConfig.For.UmbracoSettings().Content.EnablePropertyValueConverters) { - return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.RelatedLinksAlias) - || propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.RelatedLinks2Alias); + return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.RelatedLinksAlias); } return false; } @@ -88,6 +90,10 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var relatedLinksData = JsonConvert.DeserializeObject>(sourceString); var relatedLinks = new List(); + if (UmbracoContext.Current == null) return source; + + var helper = new UmbracoHelper(UmbracoContext.Current); + foreach (var linkData in relatedLinksData) { var relatedLink = new RelatedLink @@ -111,7 +117,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var udiAttempt = strLinkId.TryConvertTo(); if (udiAttempt.Success) { - var content = udiAttempt.Result.ToPublishedContent(); + var content = helper.TypedContent(udiAttempt.Result); if (content != null) { relatedLink.Id = content.Id; diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs index b5c3d850d2..8decd0100a 100644 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs +++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs @@ -12,7 +12,11 @@ namespace Umbraco.Web.PublishedCache /// Provides access to cached contents in a specified context. /// public abstract class ContextualPublishedCache - { + { + //TODO: We need to add: + //* GetById(Guid contentId) + //* GetById(UDI contentId) + protected readonly UmbracoContext UmbracoContext; /// @@ -35,6 +39,18 @@ namespace Umbraco.Web.PublishedCache return GetById(UmbracoContext.InPreviewMode, contentId); } + /// + /// Gets a content identified by its unique identifier. + /// + /// The content unique identifier. + /// The content, or null. + /// Considers published or unpublished content depending on context. + public IPublishedContent GetById(Guid contentId) + { + var intId = UmbracoContext.Application.Services.EntityService.GetIdForKey(contentId, UmbracoObjectTypes.Document); + return GetById(intId.Success ? intId.Result : -1); + } + /// /// Gets a content identified by its unique identifier. /// diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index f3cc101b21..a4793f9a67 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -10,6 +10,7 @@ using Examine; using Examine.LuceneEngine.SearchCriteria; using Examine.Providers; using Lucene.Net.Documents; +using Lucene.Net.Store; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Dynamics; @@ -173,6 +174,11 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // the manager will return the singleton without throwing initialization errors, however if examine isn't configured correctly a null // reference error will occur because the examine settings are null. } + catch (AlreadyClosedException) + { + //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot + //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. + } } return null; } @@ -211,13 +217,23 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var result = searchProvider.Search(filter.Compile()).FirstOrDefault(); if (result != null) return ConvertFromSearchResult(result); } - catch (FileNotFoundException ex) + catch (Exception ex) { - //Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco - //See this thread: http://examine.cdodeplex.com/discussions/264341 - //Catch the exception here for the time being, and just fallback to GetMedia - //TODO: Need to fix examine in LB scenarios! - LogHelper.Error("Could not load data from Examine index for media", ex); + if (ex is FileNotFoundException) + { + //Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco + //See this thread: http://examine.cdodeplex.com/discussions/264341 + //Catch the exception here for the time being, and just fallback to GetMedia + //TODO: Need to fix examine in LB scenarios! + LogHelper.Error("Could not load data from Examine index for media", ex); + } + else if (ex is AlreadyClosedException) + { + //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot + //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. + LogHelper.Error("Could not load data from Examine index for media, the app domain is most likely in a shutdown state", ex); + } + else throw; } } diff --git a/src/Umbraco.Web/PublishedContentQueryExtensions.cs b/src/Umbraco.Web/PublishedContentQueryExtensions.cs new file mode 100644 index 0000000000..c40daf5e82 --- /dev/null +++ b/src/Umbraco.Web/PublishedContentQueryExtensions.cs @@ -0,0 +1,23 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Models; + +namespace Umbraco.Web +{ + public static class PublishedContentQueryExtensions + { + /// + /// Gets a content item from the cache + /// + /// + /// + /// + public static IPublishedContent TypedContent(this ITypedPublishedContentQuery contentQuery, Udi id) + { + var guidUdi = id as GuidUdi; + if (guidUdi == null) + throw new InvalidOperationException("UDIs for content items must be " + typeof(GuidUdi)); + return contentQuery.TypedContent(guidUdi.Guid); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs index 05b6b0e468..afbfe1c4f6 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs @@ -17,13 +17,14 @@ using umbraco; using umbraco.cms.businesslogic.web; using umbraco.cms.businesslogic.language; using umbraco.cms.businesslogic.member; +using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Security; using RenderingEngine = Umbraco.Core.RenderingEngine; namespace Umbraco.Web.Routing { - internal class PublishedContentRequestEngine + internal class PublishedContentRequestEngine { private readonly PublishedContentRequest _pcr; private readonly RoutingContext _routingContext; @@ -518,51 +519,69 @@ namespace Umbraco.Web.Routing const string tracePrefix = "FollowInternalRedirects: "; if (_pcr.PublishedContent == null) - throw new InvalidOperationException("There is no PublishedContent."); + throw new InvalidOperationException("There is no PublishedContent."); + + // don't try to find a redirect if the property doesn't exist + if (_pcr.PublishedContent.HasProperty(Constants.Conventions.Content.InternalRedirectId) == false) + return false; + + var redirect = false; + IPublishedContent internalRedirectNode = null; + var internalRedirectId = + _pcr.PublishedContent.GetPropertyValue(Constants.Conventions.Content.InternalRedirectId, -1); + var valueValid = false; + if (internalRedirectId > 0) + { + valueValid = true; + // Try and get the redirect node from a legacy integer ID + internalRedirectNode = _routingContext.UmbracoContext.ContentCache.GetById(internalRedirectId); + } + else + { + var udiInternalRedirectId = + _pcr.PublishedContent.GetPropertyValue(Constants.Conventions.Content.InternalRedirectId); + if (udiInternalRedirectId != null) + { + valueValid = true; + // Try and get the redirect node from a UDI Guid + internalRedirectNode = + _routingContext.UmbracoContext.ContentCache.GetById(udiInternalRedirectId.Guid); + } + } + + if (valueValid == false) + { + // bad redirect - log and display the current page (legacy behavior) + ProfilingLogger + .Logger.Debug( + "{0}Failed to redirect, value of '{1}' is not an int nor a GuidUdi", + () => tracePrefix, () => Constants.Conventions.Content.InternalRedirectId); + } - bool redirect = false; - var internalRedirect = _pcr.PublishedContent.GetPropertyValue(Constants.Conventions.Content.InternalRedirectId); + if (internalRedirectNode == null) + { + ProfilingLogger.Logger.Debug( + "{0}Failed to redirect, value of '{1}' does not lead to a published document", () => tracePrefix, + () => Constants.Conventions.Content.InternalRedirectId); + } + else if (internalRedirectNode.Id == _pcr.PublishedContent.Id) + { + // redirect to self + ProfilingLogger.Logger.Debug("{0}Redirecting to self, ignore", + () => tracePrefix); + } + else + { + // Redirect to another page + _pcr.SetInternalRedirectPublishedContent(internalRedirectNode); + redirect = true; + ProfilingLogger.Logger.Debug("{0}Redirecting to id={1}", () => tracePrefix, + () => internalRedirectNode.Id); + } - if (string.IsNullOrWhiteSpace(internalRedirect) == false) - { - ProfilingLogger.Logger.Debug("{0}Found umbracoInternalRedirectId={1}", () => tracePrefix, () => internalRedirect); - - int internalRedirectId; - if (int.TryParse(internalRedirect, out internalRedirectId) == false) - internalRedirectId = -1; - - if (internalRedirectId <= 0) - { - // bad redirect - log and display the current page (legacy behavior) - //_pcr.Document = null; // no! that would be to force a 404 - ProfilingLogger.Logger.Debug("{0}Failed to redirect to id={1}: invalid value", () => tracePrefix, () => internalRedirect); - } - else if (internalRedirectId == _pcr.PublishedContent.Id) - { - // redirect to self - ProfilingLogger.Logger.Debug("{0}Redirecting to self, ignore", () => tracePrefix); - } - else - { - // redirect to another page - var node = _routingContext.UmbracoContext.ContentCache.GetById(internalRedirectId); - - if (node != null) - { - _pcr.SetInternalRedirectPublishedContent(node); // don't use .PublishedContent here - redirect = true; - ProfilingLogger.Logger.Debug("{0}Redirecting to id={1}", () => tracePrefix, () => internalRedirectId); - } - else - { - ProfilingLogger.Logger.Debug("{0}Failed to redirect to id={1}: no such published document", () => tracePrefix, () => internalRedirectId); - } - } - } - - return redirect; + return redirect; } - + /// /// Ensures that access to current node is permitted. /// @@ -719,16 +738,31 @@ namespace Umbraco.Web.Routing /// As per legacy, if the redirect does not work, we just ignore it. private void FollowExternalRedirect() { - if (_pcr.HasPublishedContent == false) return; + if (_pcr.HasPublishedContent == false) return; + + // don't try to find a redirect if the property doesn't exist + if (_pcr.PublishedContent.HasProperty(Constants.Conventions.Content.Redirect) == false) + return; + + var redirectId = _pcr.PublishedContent.GetPropertyValue(Constants.Conventions.Content.Redirect, -1); - var redirectId = _pcr.PublishedContent.GetPropertyValue(Constants.Conventions.Content.Redirect, -1); var redirectUrl = "#"; if (redirectId > 0) - redirectUrl = _routingContext.UrlProvider.GetUrl(redirectId); + { + redirectUrl = _routingContext.UrlProvider.GetUrl(redirectId); + } + else + { + // might be a UDI instead of an int Id + var redirectUdi = _pcr.PublishedContent.GetPropertyValue(Constants.Conventions.Content.Redirect); + if (redirectUdi != null) + redirectUrl = _routingContext.UrlProvider.GetUrl(redirectUdi.Guid); + } if (redirectUrl != "#") + { _pcr.SetRedirect(redirectUrl); + } } - #endregion } } diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index bf49e335f6..24359d2b50 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -88,6 +88,22 @@ namespace Umbraco.Web.Scheduling } var result = await wc.SendAsync(request, token); + var content = await result.Content.ReadAsStringAsync(); + + if (result.IsSuccessStatusCode) + { + LogHelper.Debug( + () => string.Format( + "Request successfully sent to url = \"{0}\". ", url)); + } + else + { + var msg = string.Format( + "Request failed with status code \"{0}\". Request content = \"{1}\".", + result.StatusCode, content); + var ex = new HttpRequestException(msg); + LogHelper.Error(msg, ex); + } } } catch (Exception e) diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index b92e79fe7b..3869f58a2a 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Templates)] [LegacyBaseTree(typeof (loadTemplates))] - [Tree(Constants.Applications.Settings, Constants.Trees.Templates, "Templates", sortOrder:1)] + [Tree(Constants.Applications.Settings, Constants.Trees.Templates, null, sortOrder:1)] [PluginController("UmbracoTrees")] [CoreTree] public class TemplatesTreeController : TreeController diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 9061f70a9d..0b5652e509 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -184,9 +184,8 @@ ..\packages\Owin.1.0\lib\net40\Owin.dll True - - ..\packages\Semver.2.0.4\lib\netstandard1.1\Semver.dll - True + + ..\packages\semver.1.1.2\lib\net45\Semver.dll System @@ -413,6 +412,7 @@ + @@ -442,7 +442,9 @@ - + + ASPXCodeBehind + diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 5befbda7cf..fec4e144fb 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -17,7 +17,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Web.Mvc; -using System.Web.Routing; using Umbraco.Core.Cache; namespace Umbraco.Web @@ -38,6 +37,7 @@ namespace Umbraco.Web private MembershipHelper _membershipHelper; private TagQuery _tag; private IDataTypeService _dataTypeService; + private IEntityService _entityService; private UrlProvider _urlProvider; private ICultureDictionary _cultureDictionary; @@ -113,6 +113,14 @@ namespace Umbraco.Web get { return _dataTypeService ?? (_dataTypeService = UmbracoContext.Application.Services.DataTypeService); } } + /// + /// Lazy instantiates the IEntityService + /// + private IEntityService EntityService + { + get { return _entityService ?? (_entityService = UmbracoContext.Application.Services.EntityService); } + } + /// /// Lazy instantiates the IUmbracoComponentRenderer if not specified in the constructor /// @@ -527,11 +535,23 @@ namespace Umbraco.Web public string UrlAbsolute(int contentId) { return UrlProvider.GetUrl(contentId, true); + } + + #endregion + + #region Members + + public IPublishedContent TypedMember(Udi id) + { + var guidUdi = id as GuidUdi; + if (guidUdi == null) return null; + return TypedMember(guidUdi.Guid); } - #endregion - - #region Members + public IPublishedContent TypedMember(Guid id) + { + return MembershipHelper.GetByProviderKey(id); + } public IPublishedContent TypedMember(object id) { @@ -593,6 +613,9 @@ namespace Umbraco.Web Guid guidId; if (ConvertIdObjectToGuid(id, out guidId)) return ContentQuery.TypedContent(guidId); + Udi udiId; + if (ConvertIdObjectToUdi(id, out udiId)) + return ContentQuery.TypedContent(udiId); return null; } @@ -615,6 +638,16 @@ namespace Umbraco.Web { return ContentQuery.TypedContent(id); } + + /// + /// Gets a content item from the cache + /// + /// + /// + public IPublishedContent TypedContent(Udi id) + { + return ContentQuery.TypedContent(id); + } /// /// Gets a content item from the cache. @@ -907,6 +940,22 @@ namespace Umbraco.Web return false; } + private static bool ConvertIdObjectToUdi(object id, out Udi guidId) + { + var s = id as string; + if (s != null) + { + return Udi.TryParse(s, out guidId); + } + if (id is Udi) + { + guidId = (Udi)id; + return true; + } + guidId = null; + return false; + } + private static bool ConvertIdsObjectToInts(IEnumerable ids, out IEnumerable intIds) { var list = new List(); @@ -937,29 +986,59 @@ namespace Umbraco.Web } guidIds = list; return true; + } + + #endregion + + #region Media + + public IPublishedContent TypedMedia(Udi id) + { + var guidUdi = id as GuidUdi; + if (guidUdi == null) return null; + return TypedMedia(guidUdi.Guid); } - #endregion + public IPublishedContent TypedMedia(Guid id) + { + //TODO: This is horrible but until the media cache properly supports GUIDs we have no choice here and + // currently there won't be any way to add this method correctly to `ITypedPublishedContentQuery` without breaking an interface and adding GUID support for media + + var entityService = UmbracoContext.Application.Services.EntityService; + var mediaAttempt = entityService.GetIdForKey(id, UmbracoObjectTypes.Media); + return mediaAttempt.Success ? ContentQuery.TypedMedia(mediaAttempt.Result) : null; + } + + /// + /// Overloaded method accepting an 'object' type + /// + /// + /// + /// + /// We accept an object type because GetPropertyValue now returns an 'object', we still want to allow people to pass + /// this result in to this method. + /// This method will throw an exception if the value is not of type int or string. + /// + public IPublishedContent TypedMedia(object id) + { + return TypedMediaForObject(id); + } - #region Media - - /// - /// Overloaded method accepting an 'object' type - /// - /// - /// - /// - /// We accept an object type because GetPropertyValue now returns an 'object', we still want to allow people to pass - /// this result in to this method. - /// This method will throw an exception if the value is not of type int or string. - /// - public IPublishedContent TypedMedia(object id) - { + private IPublishedContent TypedMediaForObject(object id) + { int intId; - return ConvertIdObjectToInt(id, out intId) ? ContentQuery.TypedMedia(intId) : null; - } - - public IPublishedContent TypedMedia(int id) + if (ConvertIdObjectToInt(id, out intId)) + return ContentQuery.TypedMedia(intId); + Guid guidId; + if (ConvertIdObjectToGuid(id, out guidId)) + return TypedMedia(guidId); + Udi udiId; + if (ConvertIdObjectToUdi(id, out udiId)) + return TypedMedia(udiId); + return null; + } + + public IPublishedContent TypedMedia(int id) { return ContentQuery.TypedMedia(id); } @@ -1489,5 +1568,10 @@ namespace Umbraco.Web return surfaceRouteParams.EncryptWithMachineKey(); } + public int GetIdForUdi(Udi udi) + { + var udiToIdAttempt = EntityService.GetIdForUdi(udi); + return udiToIdAttempt.Success ? udiToIdAttempt.Result : -1; + } } } diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs index 5afc7cee19..a19123f35e 100644 --- a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs @@ -3,7 +3,6 @@ using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; namespace Umbraco.Web.WebApi.Binders { @@ -33,7 +32,7 @@ namespace Umbraco.Web.WebApi.Binders var contentType = ApplicationContext.Services.ContentTypeService.GetContentType(model.ContentTypeAlias); if (contentType == null) { - throw new InvalidOperationException("No content type found wth alias " + model.ContentTypeAlias); + throw new InvalidOperationException("No content type found with alias " + model.ContentTypeAlias); } return new Content(model.Name, model.ParentId, contentType); } diff --git a/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs index 726994d43e..0196c5c6f6 100644 --- a/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs @@ -3,7 +3,6 @@ using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; namespace Umbraco.Web.WebApi.Binders { @@ -29,12 +28,12 @@ namespace Umbraco.Web.WebApi.Binders protected override IMedia CreateNew(MediaItemSave model) { - var contentType = ApplicationContext.Services.ContentTypeService.GetMediaType(model.ContentTypeAlias); - if (contentType == null) + var mediaType = ApplicationContext.Services.ContentTypeService.GetMediaType(model.ContentTypeAlias); + if (mediaType == null) { - throw new InvalidOperationException("No content type found wth alias " + model.ContentTypeAlias); + throw new InvalidOperationException("No media type found with alias " + model.ContentTypeAlias); } - return new Core.Models.Media(model.Name, model.ParentId, contentType); + return new Core.Models.Media(model.Name, model.ParentId, mediaType); } protected override ContentItemDto MapFromPersisted(MediaItemSave model) diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index 871f1512ef..fe93fb1e06 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -103,7 +103,7 @@ namespace Umbraco.Web.WebServices : get(svce, oldname); if (currentView == null) - currentView = new PartialView(filename); + currentView = new PartialView(PartialViewType.PartialView, filename); else currentView.Path = filename; currentView.Content = contents; diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index 01ce6a3f92..b7625770a3 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -26,12 +26,8 @@ - + - - - - - + \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index a96863ea09..54bb1ac2f1 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -511,7 +511,7 @@ namespace umbraco private void ClearContextCache() { var items = HttpContextItems; - if (items == null || items.Contains(XmlContextContentItemKey)) return; + if (items == null || items.Contains(XmlContextContentItemKey) == false) return; items.Remove(XmlContextContentItemKey); } diff --git a/src/Umbraco.Web/umbraco.presentation/helper.cs b/src/Umbraco.Web/umbraco.presentation/helper.cs index 2bee9d1e3a..ffb6151887 100644 --- a/src/Umbraco.Web/umbraco.presentation/helper.cs +++ b/src/Umbraco.Web/umbraco.presentation/helper.cs @@ -137,12 +137,16 @@ namespace umbraco attributeValue = StateHelper.GetCookieValue(keyName); break; case "#": + if (pageElements == null) + pageElements = GetPageElements(); if (pageElements[keyName] != null) attributeValue = pageElements[keyName].ToString(); else attributeValue = ""; break; case "$": + if (pageElements == null) + pageElements = GetPageElements(); if (pageElements[keyName] != null && pageElements[keyName].ToString() != string.Empty) { attributeValue = pageElements[keyName].ToString(); @@ -190,6 +194,14 @@ namespace umbraco return attributeValue; } + private static IDictionary GetPageElements() + { + IDictionary pageElements = null; + if (HttpContext.Current.Items["pageElements"] != null) + pageElements = (IDictionary)HttpContext.Current.Items["pageElements"]; + return pageElements; + } + [UmbracoWillObsolete("We should really obsolete that one.")] public static string SpaceCamelCasing(string text) { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadRelationTypes.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadRelationTypes.cs index d94ff6942e..a173926257 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadRelationTypes.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadRelationTypes.cs @@ -89,7 +89,7 @@ namespace umbraco /// the 'Relation Types' root node protected override void CreateRootNode(ref XmlTreeNode rootNode) { - rootNode.Text = "Relation Types"; + //rootNode.Text = "Relation Types"; rootNode.Icon = BaseTree.FolderIcon; rootNode.OpenIcon = BaseTree.FolderIconOpen; rootNode.NodeType = this.TreeAlias; // (Was prefixed with init) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs index abb1299507..9f810c01b8 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs @@ -46,7 +46,7 @@ namespace umbraco fileName += ".cshtml"; } - var model = new PartialView(fileName); + var model = new PartialView(IsPartialViewMacro ? PartialViewType.PartialViewMacro : PartialViewType.PartialView, fileName); var fileService = (FileService)ApplicationContext.Current.Services.FileService; var macroService = ApplicationContext.Current.Services.MacroService; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx index 270e58fd19..b66388d3a0 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx @@ -117,7 +117,7 @@ Remember: .xslt and .ascx files for your macros - will be added automaticly, but you will still need to add assemblies, + will be added automatically, but you will still need to add assemblies, images and script files manually to the list below. diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx index 815a420f88..75b5d6e66a 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/PermissionEditor.aspx @@ -1,5 +1,5 @@ <%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="../masterpages/umbracoPage.Master" CodeBehind="PermissionEditor.aspx.cs" Inherits="umbraco.cms.presentation.user.PermissionEditor" %> - +<%@ Import Namespace="Umbraco.Web" %> <%@ Register Src="../controls/Tree/TreeControl.ascx" TagName="TreeControl" TagPrefix="umbraco" %> <%@ Register Src="NodePermissions.ascx" TagName="NodePermissions" TagPrefix="user" %> <%@ Register TagPrefix="ui" Namespace="umbraco.uicontrols" Assembly="controls" %> @@ -27,7 +27,7 @@