diff --git a/.gitignore b/.gitignore index 7f7fd2bf6e..808b964260 100644 --- a/.gitignore +++ b/.gitignore @@ -46,13 +46,7 @@ src/Umbraco.Web.UI/Web.*.config.transformed umbraco/presentation/umbraco/plugins/uComponents/uComponentsInstaller.ascx umbraco/presentation/packages/uComponents/MultiNodePicker/CustomTreeService.asmx -_BuildOutput/* *.ncrunchsolution -build/UmbracoCms.AllBinaries*zip -build/UmbracoCms.WebPI*zip -build/UmbracoCms*zip -build/UmbracoExamine.PDF*zip -build/*.nupkg src/Umbraco.Tests/config/applications.config src/Umbraco.Tests/config/trees.config src/Umbraco.Web.UI/web.config @@ -68,9 +62,9 @@ src/packages/repositories.config src/Umbraco.Web.UI/[Ww]eb.config *.transformed -webpihash.txt node_modules +lib-bower src/Umbraco.Web.UI/[Uu]mbraco/[Ll]ib/* src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/umbraco.* @@ -95,7 +89,6 @@ src/Umbraco.Web.UI/[Uu]mbraco/[Aa]ssets/* src/Umbraco.Web.UI.Client/[Bb]uild/* src/Umbraco.Web.UI.Client/[Bb]uild/[Bb]elle/ src/Umbraco.Web.UI/[Uu]ser[Cc]ontrols/ -build/_BuildOutput/ src/Umbraco.Web.UI.Client/src/[Ll]ess/*.css tools/NDepend/ @@ -130,7 +123,6 @@ src/*.boltdata/ /src/Umbraco.Web.UI/Umbraco/Js/canvasdesigner.config.js /src/Umbraco.Web.UI/Umbraco/Js/canvasdesigner.front.js src/umbraco.sln.ide/* -build/UmbracoCms.*/ src/.vs/ src/Umbraco.Web.UI/umbraco/js/install.loader.js src/Umbraco.Tests/media @@ -140,9 +132,11 @@ apidocs/api/* build/docs.zip build/ui-docs.zip build/csharp-docs.zip -build/msbuild.log .vs/ src/packages/ src/PrecompiledWeb/* -build/tools/ -src/PrecompiledWeb/* + + +build.out/ +build.tmp/ +build/Modules/*/temp/ \ No newline at end of file diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000000..bc8ab500b1 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,149 @@ +Umbraco Cms Build +-- +---- + +# Quick! + +To build Umbraco, fire PowerShell and move to Umbraco's repository root (the directory that contains `src`, `build`, `README.md`...). There, trigger the build with the following command: + + build\build.ps1 + +By default, this builds the current version. It is possible to specify a different version as a parameter to the build script: + + build\build.ps1 7.6.44 + +Valid version strings are defined in the `Set-UmbracoVersion` documentation below. + +## Notes + +Git might have issues dealing with long file paths during build. You may want/need to enable `core.longpaths` support (see [this page](https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path) for details). + +# Build + +The Umbraco Build solution relies on a PowerShell module. The module needs to be imported into PowerShell. From within Umbraco's repository root: + + build\build.ps1 -ModuleOnly + +Or the abbreviated form: + + build\build.ps1 -mo + +Once the module has been imported, a set of commands are added to PowerShell. + +## Get-UmbracoBuildEnv + +Gets the Umbraco build environment ie NuGet, Semver, Visual Studio, etc. Downloads things that can be downloaded such as NuGet. Examples: + + $uenv = Get-UmbracoBuildEnv + Write-Host $uenv.SolutionRoot + &$uenv.NuGet help + +The object exposes the following properties: + +* `SolutionRoot`: the absolute path to the solution root +* `VisualStudio`: a Visual Studio object (see below) +* `NuGet`: the absolute path to the NuGet executable +* `Zip`: the absolute path to the 7Zip executable +* `VsWhere`: the absolute path to the VsWhere executable +* `NodePath`: the absolute path to the Node install +* `NpmPath`: the absolute path to the Npm install + +The Visual Studio object is `null` when Visual Studio has not been detected (eg on VSTS). When not null, the object exposes the following properties: + +* `Path`: Visual Studio installation path (eg some place under `Program Files`) +* `Major`: Visual Studio major version (eg `15` for VS 2017) +* `Minor`: Visual Studio minor version +* `MsBUild`: the absolute path to the MsBuild executable + +## Get-UmbracoVersion + +Gets an object representing the current Umbraco version. Example: + + $v = Get-UmbracoVersion + Write-Host $v.Semver + +The object exposes the following properties: + +* `Semver`: the semver object representing the version +* `Release`: the main part of the version (eg `7.6.33`) +* `Comment`: the pre release part of the version (eg `alpha02`) +* `Build`: the build number part of the version (eg `1234`) + +## Set-UmbracoVersion + +Modifies Umbraco files with the new version. + +>This entirely replaces the legacy `UmbracoVersion.txt` file. + +The version must be a valid semver version. It can include a *pre release* part (eg `alpha02`) and/or a *build number* (eg `1234`). Examples: + + Set-UmbracoVersion 7.6.33 + Set-UmbracoVersion 7.6.33-alpha02 + Set-UmbracoVersion 7.6.33+1234 + Set-UmbracoVersion 7.6.33-beta05+5678 + +Note that `Set-UmbracoVersion` enforces a slightly more restrictive naming scheme than what semver would tolerate. The pre release part can only be composed of a-z and 0-9, therefore `alpha033` is considered valid but not `alpha.033` nor `alpha033-preview` nor `RC2` (would need to be lowercased `rc2`). + +>It is considered best to add trailing zeroes to pre releases, else NuGet gets the order of versions wrong. So if you plan to have more than 10, but no more that 100 alpha versions, number the versions `alpha00`, `alpha01`, etc. + +## Build-Umbraco + +Builds Umbraco. Temporary files are generated in `build.tmp` while the actual artifacts (zip files, NuGet packages...) are produced in `build.out`. Example: + + Build-Umbraco + +Some log files, such as MsBuild logs, are produced in `build.tmp` too. The `build` directory should remain clean during a build. + +### web.config + +Building Umbraco requires a clean `web.config` file in the `Umbraco.Web.UI` project. If a `web.config` file already exists, the `pre-build` task (see below) will save it as `web.config.temp-build` and replace it with a clean copy of `web.Template.config`. The original file is replaced once it is safe to do so, by the `pre-packages` task. + +## Build-UmbracoDocs + +Builds umbraco documentation. Temporary files are generated in `build.tmp` while the actual artifacts (docs...) are produced in `build.out`. Example: + + Build-UmbracoDocs + +Some log files, such as MsBuild logs, are produced in `build.tmp` too. The `build` directory should remain clean during a build. + +## Verify-NuGet + +Verifies that projects all require the same version of their dependencies, and that NuSpec files require versions that are consistent with projects. Example: + + Verify-NuGet + +# VSTS + +Continuous integration, nightly builds and release builds run on VSTS. + +VSTS uses the `Build-Umbraco` command several times, each time passing a different *target* parameter. The supported targets are: + +* `pre-build`: prepares the build +* `compile-belle`: compiles Belle +* `compile-umbraco`: compiles Umbraco +* `pre-tests`: prepares the tests +* `compile-tests`: compiles the tests +* `pre-packages`: prepares the packages +* `pkg-zip`: creates the zip files +* `pre-nuget`: prepares NuGet packages +* `pkg-nuget`: creates NuGet packages + +All these targets are executed when `Build-Umbraco` is invoked without a parameter (or with the `all` parameter). On VSTS, compilations (of Umbraco and tests) are performed by dedicated VSTS tasks. Similarly, creating the NuGet packages is also performed by dedicated VSTS tasks. + +Finally, the produced artifacts are published in two containers that can be downloaded from VSTS: `zips` contains the zip files while `nuget` contains the NuGet packages. + +>During a VSTS build, some environment `UMBRACO_*` variables are exported by the `pre-build` target and can be reused in other targets *and* in VSTS tasks. The `UMBRACO_TMP` environment variable is used in `Umbraco.Tests` to disable some tests that have issues with VSTS at the moment. + +# Notes + +*This part needs to be cleaned up* + +Nightlies should use some sort of build number. + +We should increment versions as soon as a version is released. Ie, as soon as `7.6.33` is released, we should `Set-UmbracoVersion 7.6.34-alpha` and push. + +NuGet / NuSpec consistency checks are performed in tests. We should move it so it is done as part of the PowerShell script even before we try to compile and run the tests. + +There are still a few commands in `build` (to build docs, install Git or cleanup the install) that will need to be migrated to PowerShell. + +/eof \ No newline at end of file diff --git a/README.md b/README.md index a9559c1850..a724b3531a 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,7 @@ Umbraco is a free open source Content Management System built on the ASP.NET pla ## Building Umbraco from source ## -The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `grunt vs` in `src\Umbraco.Web.UI.Client`. - -If you're interested in making changes to Belle without running Visual Studio make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md). +The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. Note that you can always [download a nightly build](http://nightly.umbraco.org/?container=umbraco-750) so you don't have to build the code yourself. diff --git a/appveyor.yml b/appveyor.yml index dc6e22edbf..4c9a33fd23 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,9 @@ version: '{build}' shallow_clone: true + +init: + - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) + build_script: - cmd: >- SET SLN=%CD% diff --git a/build.bat b/build.bat new file mode 100644 index 0000000000..da9c4e137d --- /dev/null +++ b/build.bat @@ -0,0 +1,15 @@ +@ECHO OFF +powershell .\build\build.ps1 + +IF ERRORLEVEL 1 ( + GOTO :error +) ELSE ( + GOTO :EOF +) + +:error +ECHO. +ECHO Can not run build\build.ps1. +ECHO If this is due to a SecurityError then make sure to run the following command from an administrator command prompt: +ECHO. +ECHO powershell Set-ExecutionPolicy -ExecutionPolicy RemoteSigned \ No newline at end of file diff --git a/build/Build.bat b/build/Build.bat deleted file mode 100644 index da34691c3d..0000000000 --- a/build/Build.bat +++ /dev/null @@ -1,207 +0,0 @@ -@ECHO OFF - -:: UMBRACO BUILD FILE - - -:: ensure we have UmbracoVersion.txt -IF NOT EXIST UmbracoVersion.txt ( - ECHO UmbracoVersion.txt is missing! - GOTO error -) - -REM Get the version and comment from UmbracoVersion.txt lines 2 and 3 -SET RELEASE= -SET COMMENT= -FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED RELEASE SET RELEASE=%%i -FOR /F "skip=2 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED COMMENT SET COMMENT=%%i - -REM process args - -SET INTEGRATION=0 -SET nuGetFolder=%CD%\..\src\packages -SET SKIPNUGET=0 - -:processArgs - -:: grab the first parameter as a whole eg "/action:start" -:: end if no more parameter -SET SWITCHPARSE=%1 -IF [%SWITCHPARSE%] == [] goto endProcessArgs - -:: get switch and value -SET SWITCH= -SET VALUE= -FOR /F "tokens=1,* delims=: " %%a IN ("%SWITCHPARSE%") DO SET SWITCH=%%a& SET VALUE=%%b - -:: route arg -IF '%SWITCH%'=='/release' GOTO argRelease -IF '%SWITCH%'=='-release' GOTO argRelease -IF '%SWITCH%'=='/comment' GOTO argComment -IF '%SWITCH%'=='-comment' GOTO argComment -IF '%SWITCH%'=='/integration' GOTO argIntegration -IF '%SWITCH%'=='-integration' GOTO argIntegration -IF '%SWITCH%'=='/nugetfolder' GOTO argNugetFolder -IF '%SWITCH%'=='-nugetfolder' GOTO argNugetFolder -IF '%SWITCH%'=='/skipnuget' GOTO argSkipNuget -IF '%SWITCH%'=='-skipnuget' GOTO argSkipNuget -ECHO "Invalid switch %SWITCH%" -GOTO error - -:: handle each arg - -:argRelease -set RELEASE=%VALUE% -SHIFT -goto processArgs - -:argComment -SET COMMENT=%VALUE% -SHIFT -GOTO processArgs - -:argIntegration -SET INTEGRATION=1 -SHIFT -GOTO processArgs - -:argNugetFolder -SET nuGetFolder=%VALUE% -SHIFT -GOTO processArgs - -:argSkipNuget -SET SKIPNUGET=1 -SHIFT -GOTO processArgs - -:endProcessArgs - -REM run - -SET VERSION=%RELEASE% -IF [%COMMENT%] EQU [] (SET VERSION=%RELEASE%) ELSE (SET VERSION=%RELEASE%-%COMMENT%) - -ECHO ################################################################ -ECHO Building Umbraco %VERSION% -ECHO ################################################################ - -SET MSBUILDPATH=C:\Program Files (x86)\MSBuild\14.0\Bin -SET MSBUILD="%MSBUILDPATH%\MsBuild.exe" -SET PATH="%MSBUILDPATH%";%PATH% - -ReplaceIISExpressPortNumber.exe ..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj %RELEASE% - -ECHO. -ECHO Removing the belle build folder and bower_components folder to make sure everything is clean as a whistle -RD ..\src\Umbraco.Web.UI.Client\build /Q /S -RD ..\src\Umbraco.Web.UI.Client\bower_components /Q /S - -ECHO. -ECHO Removing existing built files to make sure everything is clean as a whistle -RMDIR /Q /S _BuildOutput -DEL /F /Q UmbracoCms.*.zip 2>NUL -DEL /F /Q UmbracoExamine.*.zip 2>NUL -DEL /F /Q UmbracoCms.*.nupkg 2>NUL -DEL /F /Q webpihash.txt 2>NUL - -ECHO. -ECHO Making sure Git is in the path so that the build can succeed -CALL InstallGit.cmd - -REM Adding the default Git path so that if it's installed it can actually be found -REM This is necessary because SETLOCAL is on in InstallGit.cmd so that one might find Git, -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 vswhere.exe for VS2017+ -IF NOT EXIST "%toolsFolder%vswhere.exe" ( - ECHO vswhere not found - fetching now - "%nuGetExecutable%" install vswhere -OutputDirectory tools -Verbosity quiet -) - -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 -"%nuGetExecutable%" help | findstr "^NuGet Version:" - -ECHO. -ECHO Restoring NuGet packages -ECHO Into %nuGetFolder% -"%nuGetExecutable%" restore "%CD%\..\src\umbraco.sln" -Verbosity Quiet -NonInteractive -PackagesDirectory "%nuGetFolder%" -IF ERRORLEVEL 1 GOTO :error - -ECHO. -ECHO. -ECHO Performing MSBuild and producing Umbraco binaries zip files -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%" /p:VSWherePath=%VSWherePath% /consoleloggerparameters:Summary;ErrorsOnly /fileLogger -IF ERRORLEVEL 1 GOTO error - -ECHO. -ECHO Setting node_modules folder to hidden to prevent VS13 from crashing on it while loading the websites project -attrib +h ..\src\Umbraco.Web.UI.Client\node_modules - -IF %SKIPNUGET% EQU 1 GOTO success - -ECHO. -ECHO Adding Web.config transform files to the NuGet package -REN .\_BuildOutput\WebApp\Views\Web.config Web.config.transform -REN .\_BuildOutput\WebApp\Xslt\Web.config Web.config.transform - -ECHO. -ECHO Packing the NuGet release files -..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.Core.nuspec -Version %VERSION% -Symbols -Verbosity quiet -..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.nuspec -Version %VERSION% -Verbosity quiet -IF ERRORLEVEL 1 GOTO error - -:success -ECHO. -ECHO No errors were detected! -ECHO There may still be some in the output, which you would need to investigate. -ECHO Warnings are usually normal. -ECHO. -ECHO. -GOTO :EOF - -:error - -ECHO. -ECHO Errors were detected! -ECHO. - -REM don't pause if continuous integration else the build server waits forever -REM before cancelling the build (and, there is noone to read the output anyways) -IF %INTEGRATION% NEQ 1 PAUSE diff --git a/build/Build.proj b/build/Build.proj deleted file mode 100644 index 1f57ca5915..0000000000 --- a/build/Build.proj +++ /dev/null @@ -1,345 +0,0 @@ - - - - - - ..\MSBuildCommunityTasks - ..\UmbracoMSBuildTasks - - - - - - - - - - - - - - - - b.ToString("x2")))); - } - } - } - } - } - ]]> - - - - - - - - - - .$(BUILD_NUMBER) - - - .$(BUILD_RELEASE) - - - .$(BUILD_RELEASE)-$(BUILD_COMMENT) - - - .$(BUILD_RELEASE)-$(BUILD_NIGHTLY) - - - .$(BUILD_RELEASE)-$(BUILD_COMMENT)-$(BUILD_NIGHTLY) - - - - Release - _BuildOutput\ - UmbracoCms$(DECIMAL_BUILD_NUMBER).zip - UmbracoCms.AllBinaries$(DECIMAL_BUILD_NUMBER).zip - UmbracoCms.WebPI$(DECIMAL_BUILD_NUMBER).zip - False - ..\..\build\$(BuildFolder) - $(MSBuildProjectDirectory)\$(BuildFolder) - $(BuildFolder)bin\ - $(BuildFolder)WebApp\ - $(BuildFolder)WebPi\ - $(BuildFolder)Configs\ - $(BuildFolderRelativeToProjects)bin\ - $(BuildFolderAbsolutePath)bin\ - $(BuildFolderRelativeToProjects)WebApp\ - $(BuildFolderAbsolutePath)WebApp\ - $(BuildFolderRelativeToProjects)WebPi\ - $(BuildFolderAbsolutePath)WebPi\ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $(BUILD_RELEASE) - $(BUILD_RELEASE)-$(BUILD_COMMENT) - $(BUILD_RELEASE)-$(BUILD_NIGHTLY) - $(BUILD_RELEASE)-$(BUILD_COMMENT)-$(BUILD_NIGHTLY) - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/BuildBelle.bat b/build/BuildBelle.bat deleted file mode 100644 index 750ffae6b2..0000000000 --- a/build/BuildBelle.bat +++ /dev/null @@ -1,68 +0,0 @@ -@ECHO OFF -SETLOCAL - :: SETLOCAL is on, so changes to the path not persist to the actual user's path - -SET toolsFolder=%CD%\tools\ -ECHO Current folder: %CD% - -SET nodeFileName=node-v6.9.1-win-x86.7z -SET nodeExtractFolder=%toolsFolder%node.js.691 - -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%')" -) - -:: 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 -) - -:: 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" - -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" - -SET drive=%CD:~0,2% -SET nuGetFolder=%drive%\packages\ -FOR /f "delims=" %%A in ('dir "%nuGetFolder%npm.*" /b') DO SET "npmPath=%nuGetFolder%%%A\" - -IF [%npmPath%] == [] GOTO :installnpm -IF NOT [%npmPath%] == [] GOTO :build - -: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.bat b/build/BuildDocs.bat deleted file mode 100644 index 9d0a04e1cd..0000000000 --- a/build/BuildDocs.bat +++ /dev/null @@ -1,20 +0,0 @@ -@ECHO OFF -SETLOCAL - -SET release=%1 -ECHO Installing Npm NuGet Package - -SET nuGetFolder=%CD%\..\src\packages\ -ECHO Configured packages folder: %nuGetFolder% -ECHO Current folder: %CD% - -%CD%\..\src\.nuget\NuGet.exe install Npm.js -OutputDirectory %nuGetFolder% -Verbosity quiet - -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\" - -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% - -Powershell.exe -ExecutionPolicy Unrestricted -File .\BuildDocs.ps1 \ No newline at end of file diff --git a/build/BuildDocs.ps1 b/build/BuildDocs.ps1 deleted file mode 100644 index 7b13f98ca4..0000000000 --- a/build/BuildDocs.ps1 +++ /dev/null @@ -1,114 +0,0 @@ -$PSScriptFilePath = (Get-Item $MyInvocation.MyCommand.Path); -$RepoRoot = (get-item $PSScriptFilePath).Directory.Parent.FullName; -$SolutionRoot = Join-Path -Path $RepoRoot "src"; -$ToolsRoot = Join-Path -Path $RepoRoot "tools"; -$DocFx = Join-Path -Path $ToolsRoot "docfx\docfx.exe" -$DocFxFolder = (Join-Path -Path $ToolsRoot "docfx") -$DocFxJson = Join-Path -Path $RepoRoot "apidocs\docfx.json" -$7Zip = Join-Path -Path $ToolsRoot "7zip\7za.exe" -$DocFxSiteOutput = Join-Path -Path $RepoRoot "apidocs\_site\*.*" -$NgDocsSiteOutput = Join-Path -Path $RepoRoot "src\Umbraco.Web.UI.Client\docs\api\*.*" -$ProgFiles86 = [Environment]::GetEnvironmentVariable("ProgramFiles(x86)"); -$MSBuild = "$ProgFiles86\MSBuild\14.0\Bin\MSBuild.exe" - - -################ Do the UI docs - -"Changing to Umbraco.Web.UI.Client folder" -cd .. -cd src\Umbraco.Web.UI.Client -Write-Host $(Get-Location) - -"Creating build folder so MSBuild doesn't run the whole grunt build" -if (-Not (Test-Path "build")) { - md "build" -} - -"Installing node" -# Check if Install-Product exists, should only exist on the build server -if (Get-Command Install-Product -errorAction SilentlyContinue) -{ - Install-Product node '' -} - -"Installing node modules" -& npm install - -"Installing grunt" -& npm install -g grunt-cli - -"Moving back to build folder" -cd .. -cd .. -cd build -Write-Host $(Get-Location) - - & grunt --gruntfile ../src/umbraco.web.ui.client/gruntfile.js docs - -# change baseUrl -$BaseUrl = "https://our.umbraco.org/apidocs/ui/" -$IndexPath = "../src/umbraco.web.ui.client/docs/api/index.html" -(Get-Content $IndexPath).replace('location.href.replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath -# zip it - -& $7Zip a -tzip ui-docs.zip $NgDocsSiteOutput -r - -################ Do the c# docs - -# 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 $?) -{ - throw "The MSBuild process returned an error code." -} -& $MSBuild "$SolutionPath" /p:Configuration=Debug /maxcpucount -if (-not $?) -{ - throw "The MSBuild process returned an error code." -} - -# Go get docfx if we don't hae it -$FileExists = Test-Path $DocFx -If ($FileExists -eq $False) { - - If(!(Test-Path $DocFxFolder)) - { - New-Item $DocFxFolder -type directory - } - - $DocFxZip = Join-Path -Path $ToolsRoot "docfx\docfx.zip" - $DocFxSource = "https://github.com/dotnet/docfx/releases/download/v1.9.4/docfx.zip" - Invoke-WebRequest $DocFxSource -OutFile $DocFxZip - - #unzip it - & $7Zip e $DocFxZip "-o$DocFxFolder" -} - -#clear site -If(Test-Path(Join-Path -Path $RepoRoot "apidocs\_site")) -{ - Remove-Item $DocFxSiteOutput -recurse -} - -# run it! -& $DocFx metadata $DocFxJson -& $DocFx build $DocFxJson - -# zip it - -& $7Zip a -tzip csharp-docs.zip $DocFxSiteOutput -r diff --git a/build/Modules/Umbraco.Build/Build-UmbracoDocs.ps1 b/build/Modules/Umbraco.Build/Build-UmbracoDocs.ps1 new file mode 100644 index 0000000000..2633026f85 --- /dev/null +++ b/build/Modules/Umbraco.Build/Build-UmbracoDocs.ps1 @@ -0,0 +1,119 @@ +# + +function Build-UmbracoDocs +{ + $uenv = Get-UmbracoBuildEnv + + $src = "$($uenv.SolutionRoot)\src" + $out = "$($uenv.SolutionRoot)\build.out" + $tmp = "$($uenv.SolutionRoot)\build.tmp" + + $buildTemp = "$PSScriptRoot\temp" + $cache = 2 + + Prepare-Build -keep $uenv + + ################ Do the UI docs + + # create Belle build folder, so that we don't cause a Belle rebuild + $belleBuildDir = "$src\Umbraco.Web.UI.Client\build" + if (-not (Test-Path $belleBuildDir)) + { + mkdir $belleBuildDir > $null + } + + Write-Host "Build UI documentation" + + # get a temp clean node env (will restore) + Sandbox-Node $uenv + + push-location "$($uenv.SolutionRoot)\src\Umbraco.Web.UI.Client" + write "node version is:" > $tmp\belle.log + &node -v >> $tmp\belle.log 2>&1 + write "npm version is:" >> $tmp\belle.log 2>&1 + &npm -v >> $tmp\belle.log 2>&1 + write "cleaning npm cache" >> $tmp\belle.log 2>&1 + &npm cache clean >> $tmp\belle.log 2>&1 + write "installing bower" >> $tmp\belle.log 2>&1 + &npm install -g bower >> $tmp\belle.log 2>&1 + write "installing gulp" >> $tmp\belle.log 2>&1 + &npm install -g gulp >> $tmp\belle.log 2>&1 + write "installing gulp-cli" >> $tmp\belle.log 2>&1 + &npm install -g gulp-cli --quiet >> $tmp\belle.log 2>&1 + write "executing npm install" >> $tmp\belle.log 2>&1 + &npm install >> $tmp\belle.log 2>&1 + write "building docs using gulp" >> $tmp\belle.log 2>&1 + &gulp docs >> $tmp\belle.log 2>&1 + pop-location + + # fixme - should we filter the log to find errors? + #get-content .\build.tmp\belle-docs.log | %{ if ($_ -match "build") { write $_}} + + # change baseUrl + $baseUrl = "https://our.umbraco.org/apidocs/ui/" + $indexPath = "$src/Umbraco.Web.UI.Client/docs/api/index.html" + (Get-Content $indexPath).Replace("location.href.replace(rUrl, indexFile)", "'$baseUrl'") ` + | Set-Content $indexPath + + # restore + Restore-Node + + # zip + &$uenv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Client\docs\api\*.*" ` + > $null + + ################ Do the c# docs + + Write-Host "Build C# documentation" + + # Build the solution in debug mode + # FIXME no only a simple compilation should be enough! + # FIXME we MUST handle msbuild & co error codes! + # FIXME deal with weird things in gitconfig? + #Build-Umbraco -Configuration Debug + Restore-NuGet $uenv + Compile-Umbraco $uenv "Debug" # FIXME different log file! + Restore-WebConfig "$src\Umbraco.Web.UI" + + # ensure we have docfx + Get-DocFx $uenv $buildTemp + + # clear + $docFxOutput = "$($uenv.SolutionRoot)\apidocs\_site" + if (test-path($docFxOutput)) + { + Remove-Directory $docFxOutput + } + + # run + $docFxJson = "$($uenv.SolutionRoot)\apidocs\docfx.json" + push-location "$($uenv.SolutionRoot)\build" # silly docfx.json wants this + + Write-Host "Run DocFx metadata" + Write-Host "Logging to $tmp\docfx.metadata.log" + &$uenv.DocFx metadata $docFxJson > "$tmp\docfx.metadata.log" + Write-Host "Run DocFx build" + Write-Host "Logging to $tmp\docfx.build.log" + &$uenv.DocFx build $docFxJson > "$tmp\docfx.build.log" + + pop-location + + # zip + &$uenv.Zip a -tzip -r "$out\csharp-docs.zip" "$docFxOutput\*.*" ` + > $null +} + +function Get-DocFx($uenv, $buildTemp) +{ + $docFx = "$buildTemp\docfx" + if (-not (test-path $docFx)) + { + Write-Host "Download DocFx..." + $source = "https://github.com/dotnet/docfx/releases/download/v2.19.2/docfx.zip" + Invoke-WebRequest $source -OutFile "$buildTemp\docfx.zip" + + &$uenv.Zip x "$buildTemp\docfx.zip" -o"$buildTemp\docfx" -aos > $nul + Remove-File "$buildTemp\docfx.zip" + } + $uenv | add-member -memberType NoteProperty -name DocFx -value "$docFx\docfx.exe" +} \ No newline at end of file diff --git a/build/Modules/Umbraco.Build/Get-UmbracoBuildEnv.ps1 b/build/Modules/Umbraco.Build/Get-UmbracoBuildEnv.ps1 new file mode 100644 index 0000000000..d1aaf7582f --- /dev/null +++ b/build/Modules/Umbraco.Build/Get-UmbracoBuildEnv.ps1 @@ -0,0 +1,180 @@ +# +# Get-UmbracoBuildEnv +# Gets the Umbraco build environment +# Downloads tools if necessary +# +function Get-UmbracoBuildEnv +{ + # store tools in the module's directory + # and cache them for two days + $path = "$PSScriptRoot\temp" + $cache = 2 + + if (-not (test-path $path)) + { + mkdir $path > $null + } + + # ensure we have NuGet + $nuget = "$path\nuget.exe" + $source = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" + if ((test-path $nuget) -and ((ls $nuget).CreationTime -lt [DateTime]::Now.AddDays(-$cache))) + { + Remove-File $nuget + } + if (-not (test-path $nuget)) + { + Write-Host "Download NuGet..." + Invoke-WebRequest $source -OutFile $nuget + } + + # ensure we have 7-Zip + $sevenZip = "$path\7za.exe" + if ((test-path $sevenZip) -and ((ls $sevenZip).CreationTime -lt [DateTime]::Now.AddDays(-$cache))) + { + Remove-File $sevenZip + } + if (-not (test-path $sevenZip)) + { + Write-Host "Download 7-Zip..." + &$nuget install 7-Zip.CommandLine -OutputDirectory $path -Verbosity quiet + $dir = ls "$path\7-Zip.CommandLine.*" | sort -property Name -descending | select -first 1 + $file = ls -path "$dir" -name 7za.exe -recurse + mv "$dir\$file" $sevenZip + Remove-Directory $dir + } + + # ensure we have vswhere + $vswhere = "$path\vswhere.exe" + if ((test-path $vswhere) -and ((ls $vswhere).CreationTime -lt [DateTime]::Now.AddDays(-$cache))) + { + Remove-File $vswhere + } + if (-not (test-path $vswhere)) + { + Write-Host "Download VsWhere..." + &$nuget install vswhere -OutputDirectory $path -Verbosity quiet + $dir = ls "$path\vswhere.*" | sort -property Name -descending | select -first 1 + $file = ls -path "$dir" -name vswhere.exe -recurse + mv "$dir\$file" $vswhere + Remove-Directory $dir + } + + # ensure we have semver + $semver = "$path\Semver.dll" + if ((test-path $semver) -and ((ls $semver).CreationTime -lt [DateTime]::Now.AddDays(-$cache))) + { + Remove-File $semver + } + if (-not (test-path $semver)) + { + Write-Host "Download Semver..." + &$nuget install semver -OutputDirectory $path -Verbosity quiet + $dir = ls "$path\semver.*" | sort -property Name -descending | select -first 1 + $file = "$dir\lib\net452\Semver.dll" + if (-not (test-path $file)) + { + Write-Error "Failed to file $file" + return + } + mv "$file" $semver + Remove-Directory $dir + } + + try + { + [Reflection.Assembly]::LoadFile($semver) > $null + } + catch + { + Write-Error -Exception $_.Exception -Message "Failed to load $semver" + break + } + + # ensure we have node + $node = "$path\node-v6.9.1-win-x86" + $source = "http://nodejs.org/dist/v6.9.1/node-v6.9.1-win-x86.7z" + if (-not (test-path $node)) + { + Write-Host "Download Node..." + Invoke-WebRequest $source -OutFile "$path\node-v6.9.1-win-x86.7z" + &$sevenZip x "$path\node-v6.9.1-win-x86.7z" -o"$path" -aos > $nul + Remove-File "$path\node-v6.9.1-win-x86.7z" + } + + # note: why? node already brings everything we need! + ## ensure we have npm + #$npm = "$path\npm.*" + #$getNpm = $true + #if (test-path $npm) + #{ + # $getNpm = $false + # $tmpNpm = ls "$path\npm.*" | sort -property Name -descending | select -first 1 + # if ($tmpNpm.CreationTime -lt [DateTime]::Now.AddDays(-$cache)) + # { + # $getNpm = $true + # } + # else + # { + # $npm = $tmpNpm.ToString() + # } + #} + #if ($getNpm) + #{ + # Write-Host "Download Npm..." + # &$nuget install npm -OutputDirectory $path -Verbosity quiet + # $npm = ls "$path\npm.*" | sort -property Name -descending | select -first 1 + # $npm.CreationTime = [DateTime]::Now + # $npm = $npm.ToString() + #} + + # find visual studio + # will not work on VSO but VSO does not need it + $vsPath = "" + $vsVer = "" + $msBuild = $null + &$vswhere | foreach { + if ($_.StartsWith("installationPath:")) { $vsPath = $_.SubString("installationPath:".Length).Trim() } + if ($_.StartsWith("installationVersion:")) { $vsVer = $_.SubString("installationVersion:".Length).Trim() } + } + if ($vsPath -ne "") + { + $vsVerParts = $vsVer.Split('.') + $vsMajor = [int]::Parse($vsVerParts[0]) + $vsMinor = [int]::Parse($vsVerParts[1]) + if ($vsMajor -eq 15) { + $msBuild = "$vsPath\MSBuild\$vsMajor.0\Bin" + } + elseif ($vsMajor -eq 14) { + $msBuild = "c:\Program Files (x86)\MSBuild\$vsMajor\Bin" + } + else + { + $msBuild = $null + } + } + + $vs = $null + if ($msBuild) + { + $vs = new-object -typeName PsObject + $vs | add-member -memberType NoteProperty -name Path -value $vsPath + $vs | add-member -memberType NoteProperty -name Major -value $vsMajor + $vs | add-member -memberType NoteProperty -name Minor -value $vsMinor + $vs | add-member -memberType NoteProperty -name MsBuild -value "$msBuild\MsBuild.exe" + } + + $solutionRoot = Get-FullPath "$PSScriptRoot\..\..\.." + + $uenv = new-object -typeName PsObject + $uenv | add-member -memberType NoteProperty -name SolutionRoot -value $solutionRoot + $uenv | add-member -memberType NoteProperty -name VisualStudio -value $vs + $uenv | add-member -memberType NoteProperty -name NuGet -value $nuget + $uenv | add-member -memberType NoteProperty -name Zip -value $sevenZip + $uenv | add-member -memberType NoteProperty -name VsWhere -value $vswhere + $uenv | add-member -memberType NoteProperty -name Semver -value $semver + $uenv | add-member -memberType NoteProperty -name NodePath -value $node + #$uenv | add-member -memberType NoteProperty -name NpmPath -value $npm + + return $uenv +} diff --git a/build/Modules/Umbraco.Build/Get-UmbracoVersion.ps1 b/build/Modules/Umbraco.Build/Get-UmbracoVersion.ps1 new file mode 100644 index 0000000000..a3ce784f14 --- /dev/null +++ b/build/Modules/Umbraco.Build/Get-UmbracoVersion.ps1 @@ -0,0 +1,26 @@ +# +# Get-UmbracoVersion +# Gets the Umbraco version +# +function Get-UmbracoVersion +{ + $uenv = Get-UmbracoBuildEnv + + # parse SolutionInfo and retrieve the version string + $filepath = "$($uenv.SolutionRoot)\src\SolutionInfo.cs" + $text = [System.IO.File]::ReadAllText($filepath) + $match = [System.Text.RegularExpressions.Regex]::Matches($text, "AssemblyInformationalVersion\(`"(.+)?`"\)") + $version = $match.Groups[1] + + # semver-parse the version string + $semver = [SemVer.SemVersion]::Parse($version) + $release = "" + $semver.Major + "." + $semver.Minor + "." + $semver.Patch + + $versions = new-object -typeName PsObject + $versions | add-member -memberType NoteProperty -name Semver -value $semver + $versions | add-member -memberType NoteProperty -name Release -value $release + $versions | add-member -memberType NoteProperty -name Comment -value $semver.PreRelease + $versions | add-member -memberType NoteProperty -name Build -value $semver.Build + + return $versions +} diff --git a/build/Modules/Umbraco.Build/Get-VisualStudio.ps1 b/build/Modules/Umbraco.Build/Get-VisualStudio.ps1 new file mode 100644 index 0000000000..cc88984eb2 --- /dev/null +++ b/build/Modules/Umbraco.Build/Get-VisualStudio.ps1 @@ -0,0 +1,30 @@ +# finds msbuild +function Get-VisualStudio($vswhere) +{ + $vsPath = "" + $vsVer = "" + &$vswhere | foreach { + if ($_.StartsWith("installationPath:")) { $vsPath = $_.SubString("installationPath:".Length).Trim() } + if ($_.StartsWith("installationVersion:")) { $vsVer = $_.SubString("installationVersion:".Length).Trim() } + } + if ($vsPath -eq "") { return $null } + + $vsVerParts = $vsVer.Split('.') + $vsMajor = [int]::Parse($vsVerParts[0]) + $vsMinor = [int]::Parse($vsVerParts[1]) + if ($vsMajor -eq 15) { + $msBuild = "$vsPath\MSBuild\$vsMajor.$vsMinor\Bin" + } + elseif ($vsMajor -eq 14) { + $msBuild = "c:\Program Files (x86)\MSBuild\$vsMajor\Bin" + } + else { return $null } + $msBuild = "$msBuild\MsBuild.exe" + + $vs = new-object -typeName PsObject + $vs | add-member -memberType NoteProperty -name Path -value $vsPath + $vs | add-member -memberType NoteProperty -name Major -value $vsMajor + $vs | add-member -memberType NoteProperty -name Minor -value $vsMinor + $vs | add-member -memberType NoteProperty -name MsBuild -value $msBuild + return $vs +} diff --git a/build/Modules/Umbraco.Build/Set-UmbracoContinuousVersion.ps1 b/build/Modules/Umbraco.Build/Set-UmbracoContinuousVersion.ps1 new file mode 100644 index 0000000000..8996777292 --- /dev/null +++ b/build/Modules/Umbraco.Build/Set-UmbracoContinuousVersion.ps1 @@ -0,0 +1,30 @@ +# +# Set-UmbracoContinuousVersion +# Sets the Umbraco version for continuous integration +# +# -Version +# where is a Semver valid version +# eg 1.2.3, 1.2.3-alpha, 1.2.3-alpha+456 +# +# -BuildNumber +# where is a string coming from the build server +# eg 34, 126, 1 +# +function Set-UmbracoContinuousVersion +{ + param ( + [Parameter(Mandatory=$true)] + [string] + $version, + [Parameter(Mandatory=$true)] + [string] + $buildNumber + ) + + Write-Host "Version is currently set to $version" + + $umbracoVersion = "$($version.Trim())-alpha$($buildNumber)" + Write-Host "Setting Umbraco Version to $umbracoVersion" + + Set-UmbracoVersion $umbracoVersion +} \ No newline at end of file diff --git a/build/Modules/Umbraco.Build/Set-UmbracoVersion.ps1 b/build/Modules/Umbraco.Build/Set-UmbracoVersion.ps1 new file mode 100644 index 0000000000..19681dcba0 --- /dev/null +++ b/build/Modules/Umbraco.Build/Set-UmbracoVersion.ps1 @@ -0,0 +1,117 @@ +# +# Set-UmbracoVersion +# Sets the Umbraco version +# +# -Version +# where is a Semver valid version +# eg 1.2.3, 1.2.3-alpha, 1.2.3-alpha+456 +# +function Set-UmbracoVersion +{ + param ( + [Parameter(Mandatory=$true)] + [string] + $version + ) + + $uenv = Get-UmbracoBuildEnv + + try + { + [Reflection.Assembly]::LoadFile($uenv.Semver) > $null + } + catch + { + Write-Error "Failed to load $uenv.Semver" + break + } + + # validate input + $ok = [Regex]::Match($version, "^[0-9]+\.[0-9]+\.[0-9]+(\-[a-z0-9]+)?(\+[0-9]+)?$") + if (-not $ok.Success) + { + Write-Error "Invalid version $version" + break + } + + # parse input + try + { + $semver = [SemVer.SemVersion]::Parse($version) + } + catch + { + Write-Error "Invalid version $version" + break + } + + # + $release = "" + $semver.Major + "." + $semver.Minor + "." + $semver.Patch + + # edit files and set the proper versions and dates + Write-Host "Update UmbracoVersion.cs" + Replace-FileText "$($uenv.SolutionRoot)\src\Umbraco.Core\Configuration\UmbracoVersion.cs" ` + "(\d+)\.(\d+)\.(\d+)(.(\d+))?" ` + "$release" + Replace-FileText "$($uenv.SolutionRoot)\src\Umbraco.Core\Configuration\UmbracoVersion.cs" ` + "CurrentComment { get { return `"(.+)`"" ` + "CurrentComment { get { return `"$($semver.PreRelease)`"" + Write-Host "Update SolutionInfo.cs" + Replace-FileText "$($uenv.SolutionRoot)\src\SolutionInfo.cs" ` + "AssemblyFileVersion\(`"(.+)?`"\)" ` + "AssemblyFileVersion(`"$release`")" + Replace-FileText "$($uenv.SolutionRoot)\src\SolutionInfo.cs" ` + "AssemblyInformationalVersion\(`"(.+)?`"\)" ` + "AssemblyInformationalVersion(`"$semver`")" + $year = [System.DateTime]::Now.ToString("yyyy") + Replace-FileText "$($uenv.SolutionRoot)\src\SolutionInfo.cs" ` + "AssemblyCopyright\(`"Copyright © Umbraco (\d{4})`"\)" ` + "AssemblyCopyright(`"Copyright © Umbraco $year`")" + + # edit csproj and set IIS Express port number + # this is a raw copy of ReplaceIISExpressPortNumber.exe + # it probably can be achieved in a much nicer way - l8tr + $source = @" + using System; + using System.IO; + using System.Xml; + using System.Globalization; + + namespace Umbraco + { + public static class PortUpdater + { + public static void Update(string path, string release) + { + XmlDocument xmlDocument = new XmlDocument(); + string fullPath = Path.GetFullPath(path); + xmlDocument.Load(fullPath); + int result = 1; + int.TryParse(release.Replace(`".`", `"`"), out result); + while (result < 1024) + result *= 10; + XmlNode xmlNode1 = xmlDocument.GetElementsByTagName(`"IISUrl`").Item(0); + if (xmlNode1 != null) + xmlNode1.InnerText = `"http://localhost:`" + (object) result; + XmlNode xmlNode2 = xmlDocument.GetElementsByTagName(`"DevelopmentServerPort`").Item(0); + if (xmlNode2 != null) + xmlNode2.InnerText = result.ToString((IFormatProvider) CultureInfo.InvariantCulture); + xmlDocument.Save(fullPath); + } + } + } +"@ + + $assem = ( + "System.Xml", + "System.IO", + "System.Globalization" + ) + + Write-Host "Update Umbraco.Web.UI.csproj" + add-type -referencedAssemblies $assem -typeDefinition $source -language CSharp + $csproj = "$($uenv.SolutionRoot)\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" + [Umbraco.PortUpdater]::Update($csproj, $release) + + return $semver +} diff --git a/build/Modules/Umbraco.Build/Umbraco.Build.psm1 b/build/Modules/Umbraco.Build/Umbraco.Build.psm1 new file mode 100644 index 0000000000..15f84c06ca --- /dev/null +++ b/build/Modules/Umbraco.Build/Umbraco.Build.psm1 @@ -0,0 +1,605 @@ + +# Umbraco.Build.psm1 +# +# $env:PSModulePath = "$pwd\build\Modules\;$env:PSModulePath" +# Import-Module Umbraco.Build -Force -DisableNameChecking +# +# PowerShell Modules: +# https://msdn.microsoft.com/en-us/library/dd878324%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 +# +# PowerShell Module Manifest: +# https://msdn.microsoft.com/en-us/library/dd878337%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 +# +# See also +# http://www.powershellmagazine.com/2014/08/15/pstip-taking-control-of-verbose-and-debug-output-part-5/ + + +. "$PSScriptRoot\Utilities.ps1" +. "$PSScriptRoot\Get-VisualStudio.ps1" + +. "$PSScriptRoot\Get-UmbracoBuildEnv.ps1" +. "$PSScriptRoot\Set-UmbracoVersion.ps1" +. "$PSScriptRoot\Set-UmbracoContinuousVersion.ps1" +. "$PSScriptRoot\Get-UmbracoVersion.ps1" +. "$PSScriptRoot\Verify-NuGet.ps1" + +. "$PSScriptRoot\Build-UmbracoDocs.ps1" + +# +# Prepares the build +# +function Prepare-Build +{ + param ( + $uenv, # an Umbraco build environment (see Get-UmbracoBuildEnv) + + [Alias("k")] + [switch] + $keep = $false + ) + + Write-Host ">> Prepare Build" + + $src = "$($uenv.SolutionRoot)\src" + $tmp = "$($uenv.SolutionRoot)\build.tmp" + $out = "$($uenv.SolutionRoot)\build.out" + + # clear + Write-Host "Clear folders and files" + + Remove-Directory "$src\Umbraco.Web.UI.Client\bower_components" + + if (-not $keep) + { + Remove-Directory "$tmp" + mkdir "$tmp" > $null + + Remove-Directory "$out" + mkdir "$out" > $null + } + + # ensure proper web.config + $webUi = "$src\Umbraco.Web.UI" + Store-WebConfig $webUi + Write-Host "Create clean web.config" + Copy-File "$webUi\web.Template.config" "$webUi\web.config" +} + +function Clear-EnvVar($var) +{ + $value = [Environment]::GetEnvironmentVariable($var) + if (test-path "env:$var") { rm "env:$var" } + return $value +} + +function Set-EnvVar($var, $value) +{ + if ($value) + { + [Environment]::SetEnvironmentVariable($var, $value) + } + else + { + if (test-path "env:$var") { rm "env:$var" } + } +} + +function Sandbox-Node +{ + param ( + $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) + ) + + $global:node_path = $env:path + $nodePath = $uenv.NodePath + $gitExe = (get-command git).Source + $gitPath = [System.IO.Path]::GetDirectoryName($gitExe) + $env:path = "$nodePath;$gitPath" + + $global:node_nodepath = Clear-EnvVar "NODEPATH" + $global:node_npmcache = Clear-EnvVar "NPM_CONFIG_CACHE" + $global:node_npmprefix = Clear-EnvVar "NPM_CONFIG_PREFIX" +} + +function Restore-Node +{ + $env:path = $node_path + + Set-EnvVar "NODEPATH" $node_nodepath + Set-EnvVar "NPM_CONFIG_CACHE" $node_npmcache + Set-EnvVar "NPM_CONFIG_PREFIX" $node_npmprefix +} + +# +# Builds the Belle UI project +# +function Compile-Belle +{ + param ( + $uenv, # an Umbraco build environment (see Get-UmbracoBuildEnv) + $version # an Umbraco version object (see Get-UmbracoVersion) + ) + + $tmp = "$($uenv.SolutionRoot)\build.tmp" + $src = "$($uenv.SolutionRoot)\src" + + Write-Host ">> Compile Belle" + Write-Host "Logging to $tmp\belle.log" + + # get a temp clean node env (will restore) + Sandbox-Node $uenv + + push-location "$($uenv.SolutionRoot)\src\Umbraco.Web.UI.Client" + write "node version is:" > $tmp\belle.log + &node -v >> $tmp\belle.log 2>&1 + write "npm version is:" >> $tmp\belle.log 2>&1 + &npm -v >> $tmp\belle.log 2>&1 + write "cleaning npm cache" >> $tmp\belle.log 2>&1 + &npm cache clean >> $tmp\belle.log 2>&1 + write "installing bower" >> $tmp\belle.log 2>&1 + &npm install -g bower >> $tmp\belle.log 2>&1 + write "installing gulp" >> $tmp\belle.log 2>&1 + &npm install -g gulp >> $tmp\belle.log 2>&1 + write "installing gulp-cli" >> $tmp\belle.log 2>&1 + &npm install -g gulp-cli --quiet >> $tmp\belle.log 2>&1 + write "executing npm install" >> $tmp\belle.log 2>&1 + &npm install >> $tmp\belle.log 2>&1 + write "executing gulp build for version $version" >> $tmp\belle.log 2>&1 + &gulp build --buildversion=$version.Release >> $tmp\belle.log 2>&1 + pop-location + + # fixme - should we filter the log to find errors? + #get-content .\build.tmp\belle.log | %{ if ($_ -match "build") { write $_}} + + # restore + Restore-Node + + # setting node_modules folder to hidden + # used to prevent VS13 from crashing on it while loading the websites project + # also makes sure aspnet compiler does not try to handle rogue files and chokes + # in VSO with Microsoft.VisualC.CppCodeProvider -related errors + # use get-item -force 'cos it might be hidden already + write "Set hidden attribute on node_modules" + $dir = get-item -force "$src\Umbraco.Web.UI.Client\node_modules" + $dir.Attributes = $dir.Attributes -bor ([System.IO.FileAttributes]::Hidden) +} + +# +# Compiles Umbraco +# +function Compile-Umbraco +{ + param ( + $uenv, # an Umbraco build environment (see Get-UmbracoBuildEnv) + [string] $buildConfiguration = "Release" + ) + + $src = "$($uenv.SolutionRoot)\src" + $tmp = "$($uenv.SolutionRoot)\build.tmp" + $out = "$($uenv.SolutionRoot)\build.out" + + if ($uenv.VisualStudio -eq $null) + { + Write-Error "Build environment does not provide VisualStudio." + break + } + + $toolsVersion = "4.0" + if ($uenv.VisualStudio.Major -eq 15) + { + $toolsVersion = "15.0" + } + + Write-Host ">> Compile Umbraco" + Write-Host "Logging to $tmp\msbuild.umbraco.log" + + # beware of the weird double \\ at the end of paths + # see http://edgylogic.com/blog/powershell-and-external-commands-done-right/ + &$uenv.VisualStudio.MsBuild "$src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" ` + /p:WarningLevel=0 ` + /p:Configuration=$buildConfiguration ` + /p:Platform=AnyCPU ` + /p:UseWPP_CopyWebApplication=True ` + /p:PipelineDependsOnBuild=False ` + /p:OutDir=$tmp\bin\\ ` + /p:WebProjectOutputDir=$tmp\WebApp\\ ` + /p:Verbosity=minimal ` + /t:Clean`;Rebuild ` + /tv:$toolsVersion ` + /p:UmbracoBuild=True ` + > $tmp\msbuild.umbraco.log + + # /p:UmbracoBuild tells the csproj that we are building from PS +} + +# +# Prepare Tests +# +function Prepare-Tests +{ + param ( + $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) + ) + + $src = "$($uenv.SolutionRoot)\src" + $tmp = "$($uenv.SolutionRoot)\build.tmp" + + Write-Host ">> Prepare Tests" + + # fixme - idea is to avoid rebuilding everything for tests + # but because of our weird assembly versioning (with .* stuff) + # everything gets rebuilt all the time... + #Copy-Files "$tmp\bin" "." "$tmp\tests" + + # data + Write-Host "Copy data files" + if( -Not (Test-Path -Path "$tmp\tests\Packaging" ) ) + { + Write-Host "Create packaging directory" + New-Item -ItemType directory -Path "$tmp\tests\Packaging" + } + Copy-Files "$src\Umbraco.Tests\Packaging\Packages" "*" "$tmp\tests\Packaging\Packages" + + # required for package install tests + if( -Not (Test-Path -Path "$tmp\tests\bin" ) ) + { + Write-Host "Create bin directory" + New-Item -ItemType directory -Path "$tmp\tests\bin" + } +} + +# +# Compiles Tests +# +function Compile-Tests +{ + param ( + $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) + ) + + $src = "$($uenv.SolutionRoot)\src" + $tmp = "$($uenv.SolutionRoot)\build.tmp" + $out = "$tmp\tests" + + $buildConfiguration = "Release" + + if ($uenv.VisualStudio -eq $null) + { + Write-Error "Build environment does not provide VisualStudio." + break + } + + $toolsVersion = "4.0" + if ($uenv.VisualStudio.Major -eq 15) + { + $toolsVersion = "15.0" + } + + Write-Host ">> Compile Tests" + Write-Host "Logging to $tmp\msbuild.tests.log" + + # beware of the weird double \\ at the end of paths + # see http://edgylogic.com/blog/powershell-and-external-commands-done-right/ + &$uenv.VisualStudio.MsBuild "$src\Umbraco.Tests\Umbraco.Tests.csproj" ` + /p:WarningLevel=0 ` + /p:Configuration=$buildConfiguration ` + /p:Platform=AnyCPU ` + /p:UseWPP_CopyWebApplication=True ` + /p:PipelineDependsOnBuild=False ` + /p:OutDir=$out\\ ` + /p:Verbosity=minimal ` + /t:Build ` + /tv:$toolsVersion ` + /p:UmbracoBuild=True ` + /p:NugetPackages=$src\packages ` + > $tmp\msbuild.tests.log + + # /p:UmbracoBuild tells the csproj that we are building from PS +} + +# +# Cleans things up and prepare files after compilation +# +function Prepare-Packages +{ + param ( + $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) + ) + + Write-Host ">> Prepare Packages" + + $src = "$($uenv.SolutionRoot)\src" + $tmp = "$($uenv.SolutionRoot)\build.tmp" + $out = "$($uenv.SolutionRoot)\build.out" + + $buildConfiguration = "Release" + + # restore web.config + Restore-WebConfig "$src\Umbraco.Web.UI" + + # cleanup build + Write-Host "Clean build" + Remove-File "$tmp\bin\*.dll.config" + Remove-File "$tmp\WebApp\bin\*.dll.config" + + # cleanup presentation + Write-Host "Cleanup presentation" + Remove-Directory "$tmp\WebApp\umbraco.presentation" + + # create directories + Write-Host "Create directories" + mkdir "$tmp\Configs" > $null + mkdir "$tmp\Configs\Lang" > $null + mkdir "$tmp\WebApp\App_Data" > $null + #mkdir "$tmp\WebApp\Media" > $null + #mkdir "$tmp\WebApp\Views" > $null + + # copy various files + Write-Host "Copy xml documentation" + cp -force "$tmp\bin\*.xml" "$tmp\WebApp\bin" + + Write-Host "Copy transformed configs and langs" + # note: exclude imageprocessor/*.config as imageprocessor pkg installs them + Copy-Files "$tmp\WebApp\config" "*.config" "$tmp\Configs" ` + { -not $_.RelativeName.StartsWith("imageprocessor") } + Copy-Files "$tmp\WebApp\config" "*.js" "$tmp\Configs" + Copy-Files "$tmp\WebApp\config\lang" "*.xml" "$tmp\Configs\Lang" + Copy-File "$tmp\WebApp\web.config" "$tmp\Configs\web.config.transform" + + Write-Host "Copy transformed web.config" + Copy-File "$src\Umbraco.Web.UI\web.$buildConfiguration.Config.transformed" "$tmp\WebApp\web.config" + + # offset the modified timestamps on all umbraco dlls, as WebResources + # break if date is in the future, which, due to timezone offsets can happen. + Write-Host "Offset dlls timestamps" + ls -r "$tmp\*.dll" | foreach { + $_.CreationTime = $_.CreationTime.AddHours(-11) + $_.LastWriteTime = $_.LastWriteTime.AddHours(-11) + } + + # copy libs + Write-Host "Copy SqlCE libraries" + Copy-Files "$src\packages\SqlServerCE.4.0.0.1" "*.*" "$tmp\bin" ` + { -not $_.Extension.StartsWith(".nu") -and -not $_.RelativeName.StartsWith("lib\") } + Copy-Files "$src\packages\SqlServerCE.4.0.0.1" "*.*" "$tmp\WebApp\bin" ` + { -not $_.Extension.StartsWith(".nu") -and -not $_.RelativeName.StartsWith("lib\") } + + # copy Belle + Write-Host "Copy Belle" + Copy-Files "$src\Umbraco.Web.UI\umbraco\assets" "*" "$tmp\WebApp\umbraco\assets" + Copy-Files "$src\Umbraco.Web.UI\umbraco\js" "*" "$tmp\WebApp\umbraco\js" + Copy-Files "$src\Umbraco.Web.UI\umbraco\lib" "*" "$tmp\WebApp\umbraco\lib" + Copy-Files "$src\Umbraco.Web.UI\umbraco\views" "*" "$tmp\WebApp\umbraco\views" + Copy-Files "$src\Umbraco.Web.UI\umbraco\preview" "*" "$tmp\WebApp\umbraco\preview" + + # prepare WebPI + Write-Host "Prepare WebPI" + Remove-Directory "$tmp\WebPi" + mkdir "$tmp\WebPi" > $null + mkdir "$tmp\WebPi\umbraco" > $null + Copy-Files "$tmp\WebApp" "*" "$tmp\WebPi\umbraco" + Copy-Files "$src\WebPi" "*" "$tmp\WebPi" +} + +# +# Creates the Zip packages +# +function Package-Zip +{ + param ( + $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) + ) + + Write-Host ">> Create Zip packages" + + $src = "$($uenv.SolutionRoot)\src" + $tmp = "$($uenv.SolutionRoot)\build.tmp" + $out = "$($uenv.SolutionRoot)\build.out" + + Write-Host "Zip all binaries" + &$uenv.Zip a -r "$out\UmbracoCms.AllBinaries.$($version.Semver).zip" ` + "$tmp\bin\*" ` + "-x!dotless.Core.*" ` + > $null + + Write-Host "Zip cms" + &$uenv.Zip a -r "$out\UmbracoCms.$($version.Semver).zip" ` + "$tmp\WebApp\*" ` + "-x!dotless.Core.*" "-x!Content_Types.xml" "-x!*.pdb"` + > $null + + Write-Host "Zip WebPI" + &$uenv.Zip a -r "$out\UmbracoCms.WebPI.$($version.Semver).zip" "-x!*.pdb"` + "$tmp\WebPi\*" ` + "-x!dotless.Core.*" ` + > $null + + # hash the webpi file + Write-Host "Hash WebPI" + $hash = Get-FileHash "$out\UmbracoCms.WebPI.$($version.Semver).zip" + Write $hash | out-file "$out\webpihash.txt" -encoding ascii +} + +# +# Prepares NuGet +# +function Prepare-NuGet +{ + param ( + $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) + ) + + Write-Host ">> Prepare NuGet" + + $src = "$($uenv.SolutionRoot)\src" + $tmp = "$($uenv.SolutionRoot)\build.tmp" + $out = "$($uenv.SolutionRoot)\build.out" + + # add Web.config transform files to the NuGet package + Write-Host "Add web.config transforms to NuGet package" + mv "$tmp\WebApp\Views\Web.config" "$tmp\WebApp\Views\Web.config.transform" + + # fixme - that one does not exist in .bat build either? + #mv "$tmp\WebApp\Xslt\Web.config" "$tmp\WebApp\Xslt\Web.config.transform" +} + +# +# Restores NuGet +# +function Restore-NuGet +{ + param ( + $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) + ) + + $src = "$($uenv.SolutionRoot)\src" + $tmp = "$($uenv.SolutionRoot)\build.tmp" + + Write-Host ">> Restore NuGet" + Write-Host "Logging to $tmp\nuget.restore.log" + + &$uenv.NuGet restore "$src\Umbraco.sln" > "$tmp\nuget.restore.log" +} + +# +# Creates the NuGet packages +# +function Package-NuGet +{ + param ( + $uenv, # an Umbraco build environment (see Get-UmbracoBuildEnv) + $version # an Umbraco version object (see Get-UmbracoVersion) + ) + + $src = "$($uenv.SolutionRoot)\src" + $tmp = "$($uenv.SolutionRoot)\build.tmp" + $out = "$($uenv.SolutionRoot)\build.out" + $nuspecs = "$($uenv.SolutionRoot)\build\NuSpecs" + + Write-Host ">> Create NuGet packages" + + # see https://docs.microsoft.com/en-us/nuget/schema/nuspec + # note - warnings about SqlCE native libs being outside of 'lib' folder, + # nothing much we can do about it as it's intentional yet there does not + # seem to be a way to disable the warning + + &$uenv.NuGet Pack "$nuspecs\UmbracoCms.Core.nuspec" ` + -Properties BuildTmp="$tmp" ` + -Version $version.Semver.ToString() ` + -Symbols -Verbosity quiet -outputDirectory $out + + &$uenv.NuGet Pack "$nuspecs\UmbracoCms.nuspec" ` + -Properties BuildTmp="$tmp" ` + -Version $version.Semver.ToString() ` + -Verbosity quiet -outputDirectory $out +} + +# +# Builds Umbraco +# +function Build-Umbraco +{ + [CmdletBinding()] + param ( + [string] + $target = "all", + [string] + $buildConfiguration = "Release" + ) + + $target = $target.ToLowerInvariant() + Write-Host ">> Build-Umbraco <$target> <$buildConfiguration>" + + Write-Host "Get Build Environment" + $uenv = Get-UmbracoBuildEnv + + Write-Host "Get Version" + $version = Get-UmbracoVersion + Write-Host "Version $($version.Semver)" + + if ($target -eq "pre-build") + { + Prepare-Build $uenv + #Compile-Belle $uenv $version + + # set environment variables + $env:UMBRACO_VERSION=$version.Semver.ToString() + $env:UMBRACO_RELEASE=$version.Release + $env:UMBRACO_COMMENT=$version.Comment + $env:UMBRACO_BUILD=$version.Build + + # set environment variable for VSO + # https://github.com/Microsoft/vsts-tasks/issues/375 + # https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + Write-Host ("##vso[task.setvariable variable=UMBRACO_VERSION;]$($version.Semver.ToString())") + Write-Host ("##vso[task.setvariable variable=UMBRACO_RELEASE;]$($version.Release)") + Write-Host ("##vso[task.setvariable variable=UMBRACO_COMMENT;]$($version.Comment)") + Write-Host ("##vso[task.setvariable variable=UMBRACO_BUILD;]$($version.Build)") + + Write-Host ("##vso[task.setvariable variable=UMBRACO_TMP;]$($uenv.SolutionRoot)\build.tmp") + } + elseif ($target -eq "pre-tests") + { + Prepare-Tests $uenv + } + elseif ($target -eq "compile-tests") + { + Compile-Tests $uenv + } + elseif ($target -eq "compile-umbraco") + { + Compile-Umbraco $uenv $buildConfiguration + } + elseif ($target -eq "pre-packages") + { + Prepare-Packages $uenv + } + elseif ($target -eq "pre-nuget") + { + Prepare-NuGet $uenv + } + elseif ($target -eq "restore-nuget") + { + Restore-NuGet $uenv + } + elseif ($target -eq "pkg-zip") + { + Package-Zip $uenv + } + elseif ($target -eq "compile-belle") + { + Compile-Belle $uenv $version + } + elseif ($target -eq "all") + { + Prepare-Build $uenv + Restore-NuGet $uenv + Compile-Belle $uenv $version + Compile-Umbraco $uenv $buildConfiguration + Prepare-Tests $uenv + Compile-Tests $uenv + # not running tests... + Prepare-Packages $uenv + Package-Zip $uenv + Verify-NuGet $uenv + Prepare-NuGet $uenv + Package-NuGet $uenv $version + } + else + { + Write-Error "Unsupported target `"$target`"." + } +} + +# +# export functions +# +Export-ModuleMember -function Get-UmbracoBuildEnv +Export-ModuleMember -function Set-UmbracoVersion +Export-ModuleMember -function Set-UmbracoContinuousVersion +Export-ModuleMember -function Get-UmbracoVersion +Export-ModuleMember -function Build-Umbraco +Export-ModuleMember -function Build-UmbracoDocs +Export-ModuleMember -function Verify-NuGet + +#eof \ No newline at end of file diff --git a/build/Modules/Umbraco.Build/Utilities.ps1 b/build/Modules/Umbraco.Build/Utilities.ps1 new file mode 100644 index 0000000000..29b3c47429 --- /dev/null +++ b/build/Modules/Umbraco.Build/Utilities.ps1 @@ -0,0 +1,120 @@ +# returns a string containing the hash of $file +function Get-FileHash($file) +{ + try + { + $crypto = new-object System.Security.Cryptography.SHA1CryptoServiceProvider + $stream = [System.IO.File]::OpenRead($file) + $hash = $crypto.ComputeHash($stream) + $text = "" + $hash | foreach ` + { + $text = $text + $_.ToString("x2") + } + return $text + } + finally + { + if ($stream) + { + $stream.Dispose() + } + $crypto.Dispose() + } +} + +# returns the full path if $file is relative to $pwd +function Get-FullPath($file) +{ + $path = [System.IO.Path]::Combine($pwd, $file) + $path = [System.IO.Path]::GetFullPath($path) + return $path +} + +# removes a directory, doesn't complain if it does not exist +function Remove-Directory($dir) +{ + remove-item $dir -force -recurse -errorAction SilentlyContinue > $null +} + +# removes a file, doesn't complain if it does not exist +function Remove-File($file) +{ + remove-item $file -force -errorAction SilentlyContinue > $null +} + +# copies a file, creates target dir if needed +function Copy-File($source, $target) +{ + $ignore = new-item -itemType file -path $target -force + cp -force $source $target +} + +# copies files to a directory +function Copy-Files($source, $select, $target, $filter) +{ + $files = ls -r "$source\$select" + $files | foreach { + $relative = $_.FullName.SubString($source.Length+1) + $_ | add-member -memberType NoteProperty -name RelativeName -value $relative + } + if ($filter -ne $null) { + $files = $files | where $filter + } + $files | + foreach { + if ($_.PsIsContainer) { + $ignore = new-item -itemType directory -path "$target\$($_.RelativeName)" -force + } + else { + Copy-File $_.FullName "$target\$($_.RelativeName)" + } + } +} + +# regex-replaces content in a file +function Replace-FileText($filename, $source, $replacement) +{ + $filepath = Get-FullPath $filename + $text = [System.IO.File]::ReadAllText($filepath) + $text = [System.Text.RegularExpressions.Regex]::Replace($text, $source, $replacement) + $utf8bom = New-Object System.Text.UTF8Encoding $true + [System.IO.File]::WriteAllText($filepath, $text, $utf8bom) +} + +# store web.config +function Store-WebConfig($webUi) +{ + if (test-path "$webUi\web.config") + { + if (test-path "$webUi\web.config.temp-build") + { + Write-Host "Found existing web.config.temp-build" + $i = 0 + while (test-path "$webUi\web.config.temp-build.$i") + { + $i = $i + 1 + } + Write-Host "Save existing web.config as web.config.temp-build.$i" + Write-Host "(WARN: the original web.config.temp-build will be restored during post-build)" + mv "$webUi\web.config" "$webUi\web.config.temp-build.$i" + } + else + { + Write-Host "Save existing web.config as web.config.temp-build" + Write-Host "(will be restored during post-build)" + mv "$webUi\web.config" "$webUi\web.config.temp-build" + } + } +} + +# restore web.config +function Restore-WebConfig($webUi) +{ + if (test-path "$webUi\web.config.temp-build") + { + Write-Host "Restoring existing web.config" + Remove-File "$webUi\web.config" + mv "$webUi\web.config.temp-build" "$webUi\web.config" + } +} \ No newline at end of file diff --git a/build/Modules/Umbraco.Build/Verify-NuGet.ps1 b/build/Modules/Umbraco.Build/Verify-NuGet.ps1 new file mode 100644 index 0000000000..1a2239393c --- /dev/null +++ b/build/Modules/Umbraco.Build/Verify-NuGet.ps1 @@ -0,0 +1,444 @@ +# +# Verify-NuGet +# + +function Format-Dependency +{ + param ( $d ) + + $m = $d.Id + " " + if ($d.MinInclude) { $m = $m + "[" } + else { $m = $m + "(" } + $m = $m + $d.MinVersion + if ($d.MaxVersion -ne $d.MinVersion) { $m = $m + "," + $d.MaxVersion } + if ($d.MaxInclude) { $m = $m + "]" } + else { $m = $m + ")" } + + return $m +} + +function Write-NuSpec +{ + param ( $name, $deps ) + + Write-Host "" + Write-Host "$name NuSpec dependencies:" + + foreach ($d in $deps) + { + $m = Format-Dependency $d + Write-Host " $m" + } +} + +function Write-Package +{ + param ( $name, $pkgs ) + + Write-Host "" + Write-Host "$name packages:" + + foreach ($p in $pkgs) + { + Write-Host " $($p.Id) $($p.Version)" + } +} + +function Verify-NuGet +{ + param ( + $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) + ) + + if ($uenv -eq $null) + { + $uenv = Get-UmbracoBuildEnv + } + + $source = @" + + using System; + using System.Collections.Generic; + using System.Linq; + using System.IO; + using System.Xml; + using System.Xml.Serialization; + using Semver; + + namespace Umbraco.Build + { + public class NuGet + { + public static Dependency[] GetNuSpecDependencies(string filename) + { + NuSpec nuspec; + var serializer = new XmlSerializer(typeof(NuSpec)); + using (var reader = new StreamReader(filename)) + { + nuspec = (NuSpec) serializer.Deserialize(reader); + } + var nudeps = nuspec.Metadata.Dependencies; + var deps = new List(); + foreach (var nudep in nudeps) + { + var dep = new Dependency(); + dep.Id = nudep.Id; + + var parts = nudep.Version.Split(','); + if (parts.Length == 1) + { + dep.MinInclude = parts[0].StartsWith("["); + dep.MaxInclude = parts[0].EndsWith("]"); + + SemVersion version; + if (!SemVersion.TryParse(parts[0].Substring(1, parts[0].Length-2).Trim(), out version)) continue; + dep.MinVersion = dep.MaxVersion = version; //parts[0].Substring(1, parts[0].Length-2).Trim(); + } + else + { + SemVersion version; + if (!SemVersion.TryParse(parts[0].Substring(1).Trim(), out version)) continue; + dep.MinVersion = version; //parts[0].Substring(1).Trim(); + if (!SemVersion.TryParse(parts[1].Substring(0, parts[1].Length-1).Trim(), out version)) continue; + dep.MaxVersion = version; //parts[1].Substring(0, parts[1].Length-1).Trim(); + dep.MinInclude = parts[0].StartsWith("["); + dep.MaxInclude = parts[1].EndsWith("]"); + } + + deps.Add(dep); + } + return deps.ToArray(); + } + + public static IEnumerable DistinctBy(/*this*/ IEnumerable source, Func keySelector) + { + HashSet knownKeys = new HashSet(); + foreach (TSource element in source) + { + if (knownKeys.Add(keySelector(element))) + { + yield return element; + } + } + } + + public static Package[] GetProjectsPackages(string src, string[] projects) + { + var l = new List(); + foreach (var project in projects) + { + var path = Path.Combine(src, project); + var packageConfig = Path.Combine(path, "packages.config"); + if (File.Exists(packageConfig)) + ReadPackagesConfig(packageConfig, l); + var csprojs = Directory.GetFiles(path, "*.csproj"); + foreach (var csproj in csprojs) + { + ReadCsProj(csproj, l); + } + } + IEnumerable p = l.OrderBy(x => x.Id); + p = DistinctBy(p, x => x.Id + ":::" + x.Version); + return p.ToArray(); + } + + public static object[] GetPackageErrors(Package[] pkgs) + { + return pkgs + .GroupBy(x => x.Id) + .Where(x => x.Count() > 1) + .ToArray(); + } + + public static object[] GetNuSpecErrors(Package[] pkgs, Dependency[] deps) + { + var d = pkgs.ToDictionary(x => x.Id, x => x.Version); + return deps + .Select(x => + { + SemVersion v; + if (!d.TryGetValue(x.Id, out v)) return null; + + var ok = true; + + /* + if (x.MinInclude) + { + if (v < x.MinVersion) ok = false; + } + else + { + if (v <= x.MinVersion) ok = false; + } + + if (x.MaxInclude) + { + if (v > x.MaxVersion) ok = false; + } + else + { + if (v >= x.MaxVersion) ok = false; + } + */ + + if (!x.MinInclude || v != x.MinVersion) ok = false; + + return ok ? null : new { Dependency = x, Version = v }; + }) + .Where(x => x != null) + .ToArray(); + } + + /* + public static Package[] GetProjectPackages(string path) + { + var l = new List(); + var packageConfig = Path.Combine(path, "packages.config"); + if (File.Exists(packageConfig)) + ReadPackagesConfig(packageConfig, l); + var csprojs = Directory.GetFiles(path, "*.csproj"); + foreach (var csproj in csprojs) + { + ReadCsProj(csproj, l); + } + return l.ToArray(); + } + */ + + public static string GetDirectoryName(string filename) + { + return Path.GetFileName(Path.GetDirectoryName(filename)); + } + + public static void ReadPackagesConfig(string filename, List packages) + { + //Console.WriteLine("read " + filename); + + PackagesConfigPackages pkgs; + var serializer = new XmlSerializer(typeof(PackagesConfigPackages)); + using (var reader = new StreamReader(filename)) + { + pkgs = (PackagesConfigPackages) serializer.Deserialize(reader); + } + foreach (var p in pkgs.Packages) + { + SemVersion version; + if (!SemVersion.TryParse(p.Version, out version)) continue; + packages.Add(new Package { Id = p.Id, Version = version, Project = GetDirectoryName(filename) }); + } + } + + public static void ReadCsProj(string filename, List packages) + { + //Console.WriteLine("read " + filename); + + // if xmlns then it's not a VS2017 with PackageReference + var text = File.ReadAllLines(filename); + var line = text.FirstOrDefault(x => x.Contains(" x.Packages != null).SelectMany(x => x.Packages)) + { + var sversion = p.VersionE ?? p.VersionA; + SemVersion version; + if (!SemVersion.TryParse(sversion, out version)) continue; + packages.Add(new Package { Id = p.Id, Version = version, Project = GetDirectoryName(filename) }); + } + } + + public class Dependency + { + public string Id { get; set; } + public SemVersion MinVersion { get; set; } + public SemVersion MaxVersion { get; set; } + public bool MinInclude { get; set; } + public bool MaxInclude { get; set; } + } + + public class Package + { + public string Id { get; set; } + public SemVersion Version { get; set; } + public string Project { get; set; } + } + + [XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd")] + [XmlRoot(Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", IsNullable = false, ElementName = "package")] + public class NuSpec + { + [XmlElement("metadata")] + public NuSpecMetadata Metadata { get; set; } + } + + [XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", TypeName = "metadata")] + public class NuSpecMetadata + { + [XmlArray("dependencies")] + [XmlArrayItem("dependency", IsNullable = false)] + public NuSpecDependency[] Dependencies { get; set; } + } + + [XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", TypeName = "dependencies")] + public class NuSpecDependency + { + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + + [XmlAttribute(AttributeName = "version")] + public string Version { get; set; } + } + + [XmlType(AnonymousType = true)] + [XmlRoot(Namespace = "", IsNullable = false, ElementName = "packages")] + public class PackagesConfigPackages + { + [XmlElement("package")] + public PackagesConfigPackage[] Packages { get; set; } + } + + [XmlType(AnonymousType = true, TypeName = "package")] + public class PackagesConfigPackage + { + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + + [XmlAttribute(AttributeName = "version")] + public string Version { get; set; } + } + + [XmlType(AnonymousType = true)] + [XmlRoot(Namespace = "", IsNullable = false, ElementName = "Project")] + public class CsProjProject + { + [XmlElement("ItemGroup")] + public CsProjItemGroup[] ItemGroups { get; set; } + } + + [XmlType(AnonymousType = true, TypeName = "ItemGroup")] + public class CsProjItemGroup + { + [XmlElement("PackageReference")] + public CsProjPackageReference[] Packages { get; set; } + } + + [XmlType(AnonymousType = true, TypeName = "PackageReference")] + public class CsProjPackageReference + { + [XmlAttribute(AttributeName = "Include")] + public string Id { get; set; } + + [XmlAttribute(AttributeName = "Version")] + public string VersionA { get; set; } + + [XmlElement("Version")] + public string VersionE { get; set;} + } + } + } + +"@ + + Write-Host ">> Verify NuGet consistency" + + $assem = ( + "System.Xml", + "System.Core", # "System.Collections.Generic" + "System.Linq", + "System.Xml.Serialization", + "System.IO", + "System.Globalization", + $uenv.Semver + ) + + try + { + # as long as the code hasn't changed it's fine to re-add, but if the code + # has changed this will throw - better warn the dev that we have an issue + add-type -referencedAssemblies $assem -typeDefinition $source -language CSharp + } + catch + { + if ($_.FullyQualifiedErrorId.StartsWith("TYPE_ALREADY_EXISTS,")) + { Write-Error "Failed to add type, did you change the code?" } + else + { Write-Error $_ } + } + if (-not $?) { break } + + $nuspecs = ( + "UmbracoCms", + "UmbracoCms.Core" + ) + + $projects = ( + "Umbraco.Core", + "Umbraco.Web", + "Umbraco.Web.UI", + "UmbracoExamine"#, + #"Umbraco.Tests", + #"Umbraco.Tests.Benchmarks" + ) + + $src = "$($uenv.SolutionRoot)\src" + $pkgs = [Umbraco.Build.NuGet]::GetProjectsPackages($src, $projects) + if (-not $?) { break } + #Write-Package "All" $pkgs + + $errs = [Umbraco.Build.NuGet]::GetPackageErrors($pkgs) + if (-not $?) { break } + + if ($errs.Length -gt 0) + { + Write-Host "" + } + foreach ($err in $errs) + { + Write-Host $err.Key + foreach ($e in $err) + { + Write-Host " $($e.Version) required by $($e.Project)" + } + } + if ($errs.Length -gt 0) + { + Write-Error "Found non-consolidated package dependencies" + break + } + + $nuerr = $false + $nupath = "$($uenv.SolutionRoot)\build\NuSpecs" + foreach ($nuspec in $nuspecs) + { + $deps = [Umbraco.Build.NuGet]::GetNuSpecDependencies("$nupath\$nuspec.nuspec") + if (-not $?) { break } + #Write-NuSpec $nuspec $deps + + $errs = [Umbraco.Build.NuGet]::GetNuSpecErrors($pkgs, $deps) + if (-not $?) { break } + + if ($errs.Length -gt 0) + { + Write-Host "" + Write-Host "$nuspec requires:" + $nuerr = $true + } + foreach ($err in $errs) + { + $m = Format-Dependency $err.Dependency + Write-Host " $m but projects require $($err.Version)" + } + } + + if ($nuerr) + { + Write-Error "Found inconsistent NuGet dependencies" + break + } +} \ No newline at end of file diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 08475e5b46..aac6f26878 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -44,65 +44,65 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 8d58ec43a3..22430a4516 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -19,22 +19,22 @@ - + - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/build/NuSpecs/tools/ReadmeUpgrade.txt b/build/NuSpecs/tools/ReadmeUpgrade.txt index 5c46ee20bf..e85b22a902 100644 --- a/build/NuSpecs/tools/ReadmeUpgrade.txt +++ b/build/NuSpecs/tools/ReadmeUpgrade.txt @@ -8,13 +8,13 @@ ---------------------------------------------------- -*** IMPORTANT NOTICE FOR 7.6 UPGRADES *** +*** IMPORTANT NOTICE FOR 7.7 UPGRADES *** Be sure to read the version specific upgrade information before proceeding: -https://our.umbraco.org/documentation/Getting-Started/Setup/Upgrading/version-specific#version-7-6-0 +https://our.umbraco.org/documentation/Getting-Started/Setup/Upgrading/version-specific#version-7-7-0 -You will most likely need to make some changes to your web.config and you will need to be -aware of the breaking changes listed there to see if these affect your installation. +Depending on the version you are upgrading from, you may need to make some changes to your web.config +and you will need to be aware of the breaking changes listed there to see if these affect your installation. Don't forget to build! diff --git a/build/ReplaceIISExpressPortNumber.exe b/build/ReplaceIISExpressPortNumber.exe deleted file mode 100644 index 04334ebffe..0000000000 Binary files a/build/ReplaceIISExpressPortNumber.exe and /dev/null differ diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt deleted file mode 100644 index ae27482bf3..0000000000 --- a/build/UmbracoVersion.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) -7.7.0 -beta003 \ No newline at end of file diff --git a/build/build.ps1 b/build/build.ps1 new file mode 100644 index 0000000000..72d8287d9c --- /dev/null +++ b/build/build.ps1 @@ -0,0 +1,67 @@ +param ( + [Parameter(Mandatory=$false)] + [string] + $version, + + [Parameter(Mandatory=$false)] + [Alias("mo")] + [switch] + $moduleOnly = $false +) + +# the script can run either from the solution root, +# or from the ./build directory - anything else fails +if ([System.IO.Path]::GetFileName($pwd) -eq "build") +{ + $mpath = [System.IO.Path]::GetDirectoryName($pwd) + "\build\Modules\" +} +else +{ + $mpath = "$pwd\build\Modules\" +} + +# look for the module and throw if not found +if (-not [System.IO.Directory]::Exists($mpath + "Umbraco.Build")) +{ + Write-Error "Could not locate Umbraco build Powershell module." + break +} + +# add the module path (if not already there) +if (-not $env:PSModulePath.Contains($mpath)) +{ + $env:PSModulePath = "$mpath;$env:PSModulePath" +} + +# force-import (or re-import) the module +Write-Host "Import Umbraco build Powershell module" +Import-Module Umbraco.Build -Force -DisableNameChecking + +# module only? +if ($moduleOnly) +{ + if (-not [string]::IsNullOrWhiteSpace($version)) + { + Write-Host "(module only: ignoring version parameter)" + } + else + { + Write-Host "(module only)" + } + break +} + +# get build environment +Write-Host "Setup Umbraco build Environment" +$uenv = Get-UmbracoBuildEnv + +# set the version if any +if (-not [string]::IsNullOrWhiteSpace($version)) +{ + Write-Host "Set Umbraco version to $version" + Set-UmbracoVersion $version +} + +# full umbraco build +Write-Host "Build Umbraco" +Build-Umbraco \ No newline at end of file diff --git a/build/setversion.ps1 b/build/setversion.ps1 new file mode 100644 index 0000000000..99f1534bf5 --- /dev/null +++ b/build/setversion.ps1 @@ -0,0 +1,18 @@ +# Usage: powershell .\setversion.ps1 7.6.8 +# Or: powershell .\setversion 7.6.8-beta001 + +param ( + [Parameter(Mandatory=$true)] + [string] + $version +) + +# report +Write-Host "Setting Umbraco version to $version" + +# import Umbraco Build PowerShell module - $pwd is ./build +$env:PSModulePath = "$pwd\Modules\;$env:PSModulePath" +Import-Module Umbraco.Build -Force -DisableNameChecking + +# run commands +$version = Set-UmbracoVersion -Version $version \ No newline at end of file diff --git a/src/.nuget/NuGet.Config b/src/.nuget/NuGet.Config deleted file mode 100644 index 6a318ad9b7..0000000000 --- a/src/.nuget/NuGet.Config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/.nuget/NuGet.exe b/src/.nuget/NuGet.exe deleted file mode 100644 index 6804fb7da7..0000000000 Binary files a/src/.nuget/NuGet.exe and /dev/null differ diff --git a/src/.nuget/NuGet.targets b/src/.nuget/NuGet.targets deleted file mode 100644 index 6ff51f6e83..0000000000 --- a/src/.nuget/NuGet.targets +++ /dev/null @@ -1,138 +0,0 @@ - - - - $(MSBuildProjectDirectory)\..\ - - - false - - - false - - - true - - - false - - - - - - - - - - - - - $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) - $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) - - - - - $(SolutionDir).nuget - packages.config - - - - - $(NuGetToolsPath)\NuGet.exe - @(PackageSource) - - "$(NuGetExePath)" - mono --runtime=v4.0.30319 $(NuGetExePath) - - $(TargetDir.Trim('\\')) - - -RequireConsent - -NonInteractive - - "$(SolutionDir) " - "$(SolutionDir)" - - - $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) - $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols - - - - RestorePackages; - $(BuildDependsOn); - - - - - $(BuildDependsOn); - BuildPackage; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj index a5d86d5377..2f2bb5866d 100644 --- a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj +++ b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj @@ -93,7 +93,6 @@ - - - - + + + + + + + + + + + + + + - - - - - - - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/js/install.js b/src/Umbraco.Web.UI/Umbraco/js/install.js deleted file mode 100644 index 41aab12c80..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/js/install.js +++ /dev/null @@ -1,30 +0,0 @@ -yepnope({ - - load: [ - 'lib/jquery/jquery-2.0.3.min.js', - - /* 1.1.5 */ - 'lib/angular/1.1.5/angular.min.js', - 'lib/angular/1.1.5/angular-cookies.min.js', - 'lib/angular/1.1.5/angular-mobile.min.js', - 'lib/angular/1.1.5/angular-mocks.js', - 'lib/angular/1.1.5/angular-sanitize.min.js', - 'lib/underscore/underscore.js', - 'js/umbraco.servervariables.js', - 'js/app.dev.js' - ], - - complete: function () { - jQuery(document).ready(function () { - - angular.module('umbraco.install', [ - 'umbraco.resources', - 'umbraco.services', - 'umbraco.httpbackend', - 'ngMobile' - ]); - - angular.bootstrap(document, ['umbraco.install']); - }); - } -}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml index c8f9ab7cd1..8b189ae1a0 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml @@ -2,10 +2,6 @@ @using Umbraco.Web.Templates @using Newtonsoft.Json.Linq -@* - Razor helpers located at the bottom of this file -*@ - @if (Model != null && Model.sections != null) { var oneColumn = ((System.Collections.ICollection)Model.sections).Count == 1; @@ -64,29 +60,21 @@ JObject cfg = contentItem.config; if(cfg != null) - foreach (JProperty property in cfg.Properties()) - { - var propertyValue = HttpUtility.HtmlAttributeEncode(property.Value.ToString()); - attrs.Add(property.Name + "=\"" + propertyValue + "\""); + foreach (JProperty property in cfg.Properties()) { + attrs.Add(property.Name + "='" + property.Value.ToString() + "'"); } - + JObject style = contentItem.styles; - if (style != null) { - var cssVals = new List(); - foreach (JProperty property in style.Properties()) - { - var propertyValue = property.Value.ToString(); - if (string.IsNullOrWhiteSpace(propertyValue) == false) - { - cssVals.Add(property.Name + ":" + propertyValue + ";"); - } - } + if (style != null) { + var cssVals = new List(); + foreach (JProperty property in style.Properties()) + cssVals.Add(property.Name + ":" + property.Value.ToString() + ";"); - if (cssVals.Any()) - attrs.Add("style=\"" + HttpUtility.HtmlAttributeEncode(string.Join(" ", cssVals)) + "\""); + if (cssVals.Any()) + attrs.Add("style='" + string.Join(" ", cssVals) + "'"); } - + return new MvcHtmlString(string.Join(" ", attrs)); } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml index 6ab5c1355a..e672aa2a11 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml @@ -2,10 +2,6 @@ @using Umbraco.Web.Templates @using Newtonsoft.Json.Linq -@* - Razor helpers located at the bottom of this file -*@ - @if (Model != null && Model.sections != null) { var oneColumn = ((System.Collections.ICollection)Model.sections).Count == 1; @@ -64,29 +60,21 @@ JObject cfg = contentItem.config; if(cfg != null) - foreach (JProperty property in cfg.Properties()) - { - var propertyValue = HttpUtility.HtmlAttributeEncode(property.Value.ToString()); - attrs.Add(property.Name + "=\"" + propertyValue + "\""); + foreach (JProperty property in cfg.Properties()) { + attrs.Add(property.Name + "='" + property.Value.ToString() + "'"); } - + JObject style = contentItem.styles; - if (style != null) { - var cssVals = new List(); - foreach (JProperty property in style.Properties()) - { - var propertyValue = property.Value.ToString(); - if (string.IsNullOrWhiteSpace(propertyValue) == false) - { - cssVals.Add(property.Name + ":" + propertyValue + ";"); - } - } + if (style != null) { + var cssVals = new List(); + foreach (JProperty property in style.Properties()) + cssVals.Add(property.Name + ":" + property.Value.ToString() + ";"); - if (cssVals.Any()) - attrs.Add("style=\"" + HttpUtility.HtmlAttributeEncode(string.Join(" ", cssVals)) + "\""); + if (cssVals.Any()) + attrs.Add("style='" + string.Join(" ", cssVals) + "'"); } - + return new MvcHtmlString(string.Join(" ", attrs)); } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Base.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Base.cshtml index ffb7603048..a86c04819a 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Base.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Base.cshtml @@ -1,4 +1,5 @@ @model dynamic +@using Umbraco.Web.Templates @functions { public static string EditorView(dynamic contentItem) diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml index c27be6bcdf..393157bcf8 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml @@ -1,2 +1,7 @@ @model dynamic -@Html.Raw(Model.value) +@using Umbraco.Web.Templates + + +
+ @Html.Raw(Model.value) +
diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Macro.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Macro.cshtml index ed08bb2484..e0822808d8 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Macro.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Macro.cshtml @@ -1,4 +1,6 @@ @inherits UmbracoViewPage +@using Umbraco.Web.Templates + @if (Model.value != null) { diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml index 5b5adbdc7d..09d04219f2 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml @@ -1,4 +1,5 @@ @model dynamic +@using Umbraco.Web.Templates @if (Model.value != null) { @@ -13,7 +14,7 @@ } } - @Model.value.altText + @Model.value.caption if (Model.value.caption != null) { diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml index 8c92ca0d83..0cac4eb1ff 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml @@ -4,9 +4,8 @@ @if (Model.editor.config.markup != null) { string markup = Model.editor.config.markup.ToString(); - var umbracoHelper = new UmbracoHelper(UmbracoContext.Current); - - markup = markup.Replace("#value#", umbracoHelper.ReplaceLineBreaksForHtml(HttpUtility.HtmlEncode(Model.value.ToString()))); + + markup = markup.Replace("#value#", Model.value.ToString()); markup = markup.Replace("#style#", Model.editor.config.style.ToString()); diff --git a/src/Umbraco.Web.UI/config/grid.editors.config.js b/src/Umbraco.Web.UI/config/grid.editors.config.js index b904920566..12fa726f21 100644 --- a/src/Umbraco.Web.UI/config/grid.editors.config.js +++ b/src/Umbraco.Web.UI/config/grid.editors.config.js @@ -1,28 +1,28 @@ -[ - { - "name": "Rich text editor", - "alias": "rte", - "view": "rte", - "icon": "icon-article" - }, - { - "name": "Image", - "alias": "media", - "view": "media", - "icon": "icon-picture" - }, - { - "name": "Macro", - "alias": "macro", - "view": "macro", - "icon": "icon-settings-alt" - }, - { - "name": "Embed", - "alias": "embed", - "view": "embed", - "icon": "icon-movie-alt" - }, +[ + { + "name": "Rich text editor", + "alias": "rte", + "view": "rte", + "icon": "icon-article" + }, + { + "name": "Image", + "alias": "media", + "view": "media", + "icon": "icon-picture" + }, + { + "name": "Macro", + "alias": "macro", + "view": "macro", + "icon": "icon-settings-alt" + }, + { + "name": "Embed", + "alias": "embed", + "view": "embed", + "icon": "icon-movie-alt" + }, { "name": "Headline", "alias": "headline", @@ -33,14 +33,14 @@ "markup": "

#value#

" } }, - { - "name": "Quote", - "alias": "quote", - "view": "textstring", - "icon": "icon-quote", - "config": { - "style": "border-left: 3px solid #ccc; padding: 10px; color: #ccc; font-family: serif; font-style: italic; font-size: 18px", - "markup": "
#value#
" - } - } + { + "name": "Quote", + "alias": "quote", + "view": "textstring", + "icon": "icon-quote", + "config": { + "style": "border-left: 3px solid #ccc; padding: 10px; color: #ccc; font-family: serif; font-style: italic; font-size: 18px", + "markup": "
#value#
" + } + } ] \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index 6687e71f4c..4e0c24434c 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -66,6 +66,8 @@ false + + true false diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 5d0779f8b6..4b0f697e4a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -12,9 +12,11 @@ Copy Create Create Package + Create group Delete Disable Empty recycle bin + Enable Export Document Type Import Document Type Import Package @@ -27,6 +29,7 @@ Unpublish Reload Republish entire site + Rename Restore Set permissions for the page %0% Choose where to move @@ -35,9 +38,12 @@ Rollback Send To Publish Send To Translation + Set group Sort Translate Update + Set permissions + Unlock Create Content Template @@ -81,14 +87,14 @@ Domain '%0%' has been updated Edit Current Domains - Inherit Culture - or inherit culture from parent nodes. Will also apply
+ or inherit culture from parent nodes. Will also apply
to the current node, unless a domain below applies too.]]>
Domains @@ -339,11 +345,11 @@ Number of columns Number of rows - Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates, + Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates, by referring this ID using a <asp:content /> element.]]> - Select a placeholder id from the list below. You can only + Select a placeholder id from the list below. You can only choose Id's from the current template's master.]]> Click on the image to see full size @@ -380,15 +386,15 @@ - %0%' below
You can add additional languages under the 'languages' in the menu on the left + %0%' below
You can add additional languages under the 'languages' in the menu on the left ]]>
Culture Name Edit the key of the dictionary item. - @@ -398,6 +404,8 @@ Confirm your password Name the %0%... Enter a name... + Enter an email... + Enter a username... Label... Enter a description... Type to search... @@ -424,6 +432,11 @@ Create custom list view Remove custom list view + + Renamed + Enter a new folder name here + %0% was renamed to %1% + Add prevalue Database datatype diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index aad1ab1b3b..f40ed5ca5c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -29,10 +29,11 @@ Unpublish Reload Republish entire site + Rename + Restore Set permissions for the page %0% Choose where to move In the tree structure below - Restore Permissions Rollback Send To Publish @@ -43,6 +44,7 @@ Update Set permissions Unlock + Create Content Template Content @@ -68,7 +70,7 @@ Allow access to change the sort order for nodes Allow access to translate a node Allow access to save a node - Allow access to create a content template + Allow access to create a Content Template Permission denied. @@ -134,6 +136,8 @@ Insert table Generate models Save and generate models + Undo + Redo To change the document type for the selected content, first select from the list of valid types for this location. @@ -184,7 +188,7 @@ Member Type No changes have been made No date chosen - Link title + Page title Properties This document is published but is not visible because the parent '%0%' is unpublished This document is published but is not in the cache @@ -212,10 +216,21 @@ Target This translates to the following time on the server: What does this mean?]]> + Are you sure you want to delete this item? + Property %0% uses editor %1% which is not supported by Nested Content. Add another text box Remove this text box Content root + + Create a new Content Template from '%0%' + Blank + Select a Content Template + Content Template created + A Content Template was created from '%0%' + Another Content Template with the same name already exists + A Content Template is pre-defined content that an editor can select to use as the basis for creating new content + Click to upload Drop your files here... @@ -368,6 +383,7 @@ Select node Select sections Select users + No icons were found There are no parameters for this macro There are no macros available to insert External login providers @@ -401,6 +417,7 @@ Name the %0%... Enter a name... Enter an email... + Enter a username... Label... Enter a description... Type to search... @@ -428,6 +445,11 @@ Create custom list view Remove custom list view + + Renamed + Enter a new folder name here + %0% was renamed to %1% + Add prevalue Database datatype @@ -587,6 +609,7 @@ Scheduled Publishing Search Sorry, we can not find what you are looking for + No items have been added Server Show Show page on Send @@ -1148,6 +1171,7 @@ To manage your website, simply open the Umbraco back office and start adding con Validation Validation errors must be fixed before the item can be saved Failed + Saved Insufficient user permissions, could not complete the operation Cancelled Operation was cancelled by a 3rd party add-in @@ -1213,6 +1237,8 @@ To manage your website, simply open the Umbraco back office and start adding con Script view not saved An error occurred saving the file. An error occurred saving the file. + Deleted %0% user groups + %0% was deleted Enabled %0% users An error occurred while enabling the users Disabled %0% users @@ -1549,7 +1575,7 @@ To manage your website, simply open the Umbraco back office and start adding con Content - Content Blueprints + Content Templates Media Cache Browser Recycle Bin @@ -1571,6 +1597,8 @@ To manage your website, simply open the Umbraco back office and start adding con Relation Types Packages Packages + Partial Views + Partial View Macro Files Python Files Install from repository Install Runway @@ -1666,6 +1694,8 @@ To manage your website, simply open the Umbraco back office and start adding con User groups has been invited An invitation has been sent to the new user with details about how to log in to Umbraco. + Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password and add a picture for your avatar. + Upload a picture to make it easy for other users to recognize you. Writer Translator Change diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index 01692dd593..3beeb33d6b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -11,11 +11,14 @@ Изменить тип документа Копировать Создать + Создать шаблон содержимого + Создать группу Создать пакет Значение по умолчанию Удалить Отключить Очистить корзину + Включить Экспортировать Импортировать Импортировать пакет @@ -26,6 +29,7 @@ Публичный доступ Опубликовать Обновить узлы + Переименовать Опубликовать весь сайт Установить разрешения для страницы '%0%' Восстановить @@ -33,11 +37,40 @@ Откатить Направить на публикацию Направить на перевод + Задать группу + Задать права Сортировать Перевести Скрыть + Разблокировать Обновить + + Содержимое + Администрирование + Структура + Другое + + + Разрешить доступ к назначению языков и доменов + Разрешить доступ к просмотру журнала истории узла + Разрешить доступ на просмотр узла + Разрешить доступ на смену типа документа для узла + Разрешить доступ к копированию узла + Разрешить доступ к созданию узлов + Разрешить доступ к удалению узлов + Разрешить доступ к перемещению узла + Разрешить доступ к установке и изменению правил публичного доступа для узла + Разрешить доступ к публикации узла + Разрешить доступ к изменению прав доступа к узлу + Разрешить доступ на возврат к предыдущим состояниям узла + Разрешить доступ к отправке узла на одобрение перед публикацией + Разрешить доступ к отправке узла на перевод данных + Разрешить доступ к изменению порядка сортировки узлов + Разрешить доступ к переводу данных узла + Разрешить доступ к сохранению узла + Разрешить доступ к созданию шаблона содержимого + Добавить новый домен Домен @@ -64,6 +97,15 @@ Наблюдать за + + Создать новый шаблон содержимого из '%0%' + Пустой + Выбрать шаблон содержимого + Шаблон содержимого создан + Создан шаблон содержимого из '%0%' + Другой шаблон содержимого с таким же названием уже существует + Шаблон содержимого — это предопределенный набор данных, который редактор может использовать для начального заполнения свойств при создании узлов содержимого + Завершено @@ -167,6 +209,7 @@ Альтернативный текст (необязательно) Элементы списка Нажмите для правки этого элемента + Начальный узел содержимого Создано пользователем Исходный автор Дата создания @@ -189,8 +232,10 @@ Включен в группу(ы) Роль участника Тип участника + Вы уверены, что хотите удалить этот элемент? + Свойство '%0%' использует редактор '%1%', который не поддерживается для вложенного содержимого. Дата не указана - Заголовок ссылки + Заголовок страницы Доступные группы Свойства Этот документ опубликован, но скрыт, потому что его родительский документ '%0%' не опубликован @@ -278,6 +323,7 @@ Где вы хотите создать новый %0% + Выберите тип документов, для которого нужно создать шаблон содержимого Создать в узле Новая папка Новый тип данных @@ -328,8 +374,12 @@ Открыть в новом окне? Свойства макроса Этот макрос не имеет редактируемых свойств + Заголовок ссылки Вставить Изменить разрешения для + Установить разрешения для + Установить права доступа к '%0%' для группы пользователей '%1%' + Выберите группу(ы) пользователей, для которых нужно установить разрешения Все элементы в корзине сейчас удаляются. Пожалуйста, не закрывайте это окно до окончания процесса удаления Корзина пуста Вы больше не сможете восстановить элементы, удаленные из корзины @@ -348,6 +398,7 @@ из нижеследующего списка. Список ограничивается идентификаторами, определенными в родительском шаблоне данного шаблона.]]> Кликните на изображении, чтобы увидеть полноразмерную версию Выберите элемент + Ссылка Просмотр элемента кэша Создать папку... Связать с оригиналом @@ -358,13 +409,18 @@ Ссылка на медиа-элемент Ссылка на файл Выбрать медиа + Выбрать начальный узел медиа-библиотеки Выбрать значок Выбрать элемент Выбрать ссылку Выбрать макрос Выбрать содержимое + Выбрать начальный узел содержимого Выбрать участника Выбрать группу участников + Выбрать узел + Выбрать разделы + Выбрать пользователей Это макрос без параметров Нет макросов, доступных для вставки в редактор Провайдеры аутентификации @@ -510,9 +566,11 @@ Ошибка Найти Начало + Группы Папка Высота Справка + Скрыть Иконка Импорт Внутренний отступ @@ -531,6 +589,7 @@ Выход Макрос Обязательно + Сообщение Больше Переместить Название @@ -538,9 +597,12 @@ След Нет из + Выкл Ok Открыть + Вкл или + Сортировка по Пароль Путь Идентификатор контейнера @@ -566,6 +628,7 @@ Показать страницу при отправке Размер Сортировать + Состояние Отправить Тип Что искать? @@ -690,7 +753,7 @@ Разрешить HTTPS Устанавливает значение параметра 'umbracoSSL' в 'true' в секции 'appSettings' файла web.config. - Параметр 'umbracoUseSSL' в секции 'appSetting' файлf web.config теперь установлен в 'true', значения cookies будут промаркированы как безопасные. + Параметр 'umbracoUseSSL' в секции 'appSetting' файла web.config теперь установлен в 'true', значения cookies будут промаркированы как безопасные. Исправить Невозможно исправление по результату проверки значения на 'ShouldNotEqual'. @@ -739,6 +802,8 @@ %0%.]]> %0%.]]> +

Зафиксированы следующие результаты автоматической проверки состояния Umbraco по расписанию, запущенной на %0% в %1%:

%2%]]>
+ Результат проверки состояния Umbraco перейти к @@ -897,7 +962,7 @@ Ссылка, по которой Вы попали сюда, неверна или устарела Umbraco: сброс пароля - Ваше имя пользователя для входа в панель администрирования Umbraco: %0%

Перейдите по этой ссылке для того, чтобы сбросить Ваш пароль, или скопируйте текст ссылки и вставьте в адресную строку своего браузера:

%1%

]]> +

Ваше имя пользователя для входа в панель администрирования Umbraco: %0%

Перейдите по этой ссылке для того, чтобы сбросить Ваш пароль, или скопируйте текст ссылки и вставьте в адресную строку своего браузера:

%1%

]]>
@@ -913,6 +978,7 @@ или нажмите сюда, чтобы выбрать файлы Разрешены только типы файлов: Максимально допустимый размер файла: + Начальный узел медиа Создать нового участника @@ -1067,10 +1133,13 @@ Подтвердите пароль - Укажите Ваш email + Укажите Ваш email... Укажите описание... + Укажите email... + Укажите сообщение... Укажите имя... Укажите теги (нажимайте Enter после каждого тега)... + Укажите имя пользователя... Укажите фильтр... Метка... Назовите %0%... @@ -1157,6 +1226,11 @@ Ссылка В новом окне + + Переименована + Укажите здесь новое название для папки + '%0%' была переименована в '%1%' + Текущая версия Красным отмечен текст, которого уже нет в последней версии, зеленым - текст, который добавлен]]> @@ -1273,6 +1347,7 @@ Стиль CSS сохранен Шаблон сохранен Произошла ошибка при сохранении пользователя (проверьте журналы ошибок) + Группа пользователей сохранена Пользователь сохранен Тип пользователей сохранен Файл не сохранен @@ -1286,10 +1361,12 @@ Отменено Операция отменена установленным сторонним расширением или блоком кода Ошибка + Сохранено Представление не сохранено Произошла ошибка при сохранении файла Представление сохранено Представление сохранено без ошибок + Права доступа сохранены для Cкрипт Python не сохранен Cкрипт Python не может быть сохранен в связи с ошибками Cкрипт Python сохранен @@ -1309,6 +1386,23 @@ XSLT-документ не может быть сохранен, проверьте установки файловых разрешений XSLT-документ сохранен Ошибок в XSLT-документе нет + Удалено %0% групп пользователей + '%0%' была удалена + Активировано %0% пользователей + При активации пользователей произошла ошибка + Заблокировано %0% пользователей + При блокировке пользователей произошла ошибка + '%0%' сейчас активирован + При активации пользователя произошла ошибка + '%0%' сейчас заблокирован + При блокировке пользователя произошла ошибка + Группы пользователей установлены + Удалено %0% групп пользователей + '%0%' была удалена + Разблокировано %0% пользователей + При разблокировке пользователей произошла ошибка + '%0%' сейчас разблокирован + При разблокировке пользователя произошла ошибка Используется синтаксис селекторов CSS, например: h1, .redHeader, .blueTex @@ -1461,6 +1555,9 @@ Это значение будет использовано только если первичное поле пусто Дата и время + + символов осталось + Задачи, назначенные Вам Аналитика Обзор кэша + Содержимое + Шаблоны содержимого Корзина Созданные пакеты Типы данных @@ -1523,6 +1622,7 @@ Языки Установить локальный пакет Макросы + Медиа-материалы Типы медиа-материалов Участники Группы участников @@ -1542,6 +1642,7 @@ Скрипты Стили CSS Шаблоны + Пользователи Файлы XSLT @@ -1551,24 +1652,53 @@ Во время проверки обновлений произошла ошибка. Пожалуйста, просмотрите журнал трассировки для получения дополнительной информации. + Доступ + На основании установленных групп и назначенных начальных узлов, пользователь имеет доступ к следующим узлам Администратор + Назначение доступа + Вернуться к пользователям Поле категории Изменить Изменить пароль Вы можете сменить свой пароль для доступа к административной панели Umbraco, заполнив нижеследующие поля и нажав на кнопку 'Изменить пароль' + Сменить аватар Подтверждение нового пароля Канал содержимого + Создать еще одного пользователя + Создан + Создать пользователя + Создавайте новых пользователей, которым нужен доступ к административной панели Umbraco. При создании пользователя для него генерируется новый первичный пароль, который нужно сообщить пользователю. Поле описания Отключить пользователя Тип документа Редактор Исключить поле + Неудачных попыток входа + К профилю пользователя + Добавьте пользователя в группу(ы) для задания прав доступа + Приглашение в панель администрирования Umbraco + +

Здравствуйте, %0%,

Вы были приглашены пользователем %1%, и Вам предоставлен доступ в панель администрирования Umbraco.

Сообщение от %1%: %2%

Перейдите по этой ссылке, чтобы принять приглашение.

Если Вы не имеете возможности перейти по ссылке, скопируйте нижеследующий текст ссылки и вставьте в адресную строку Вашего браузера.

%3%

]]> +
+ Пригласить еще одного пользователя + Пригласить пользователя + Пригласите новых пользователей, которым нужен доступ к административной панели Umbraco. Приглашенному будет направлено электронное письмо с инструкциями по доступу к Umbraco. Язык + Установите язык отображения интерфейса администрирования + Время последней блокировки + Время последнего входа + Пароль в последний раз менялся Имя входа (логин) - Начальный узел в Медиа-библиотеке + Начальный узел медиа-библиотеки + Можно ограничить доступ к медиа-библиотеке (какой-либо ее части), задав начальный узел + Начальные узлы медиа-библиотеки + Можно ограничить доступ к медиа-библиотеке (каким-либо ее частям), задав перечень начальных узлов Разделы Новый пароль Отключить доступ к административной панели Umbraco + пока еще не входил + пока не блокировался + Пароль не менялся Прежний пароль Пароль Ваш пароль доступа изменен! @@ -1582,12 +1712,34 @@ Заменить разрешения для дочерних документов Вы изменяете разрешения для следующих документов: Выберите документы для изменения их разрешений + Удалить аватар + Права доступа по-умолчанию + Атомарные права доступа + Можно установить права доступа к конкретным узлам + Профиль Сбросить пароль Поиск всех дочерних документов + Отправить приглашение Сессия истекает через - Начальный узел содержимого + Разделы, доступные пользователю + Начальный узел не задан + Начальные узлы не заданы + Начальный узел содержимого + Можно ограничить доступ к дереву содержимого (какой-либо его части), задав начальный узел + Начальные узлы содержимого + Можно ограничить доступ к дереву содержимого (каким-либо его частям), задав перечень начальных узлов + Был создан + Новый первичный пароль успешно сгенерирован. Для входа используйте пароль, приведенный ниже. Переводчик + Время последнего изменения Имя пользователя + Группа пользователей + Права доступа для группы + Группы пользователей + был приглашен + Новому пользователю было отправлено приглашение, в котором содержатся инструкции для входа в панель Umbraco. + Здравствуйте и добро пожаловать в Umbraco! Все будет готово в течении пары минут, нам лишь нужно задать Ваш пароль для входа и добавить аватар. + Загрузите изображение, это поможет другим пользователям идентифицировать Вас. Разрешения для пользователя Автор Ваша недавняя история diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index de2c632b74..aa5e617cd1 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -636,8 +636,12 @@ Återställ + Definiera beskräning + Ge beskärningen ett alias och dess standardbredd och -höjd + spara beskärning + Lägg till ny beskärning - + Nuvarande version Röd text kommer inte att synas i den valda versionen. , Grön betyder att den har tillkommit]]> Dokumentet har återgått till en tidigare version diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index bfb1974f2f..27fbc6d5c5 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -373,7 +373,7 @@ namespace Umbraco.Web.Cache { DistributedCache.Instance.RemoveUnpublishedPageCache(e.DeletedEntities.ToArray()); } - + /// /// Handles cache refreshing for when content is saved (not published) /// @@ -767,7 +767,6 @@ namespace Umbraco.Web.Cache { var handler = FindHandler(e); if (handler == null) continue; - handler.Invoke(null, new[] { e.Sender, e.Args }); } } diff --git a/src/Umbraco.Web/Cache/PageCacheRefresher.cs b/src/Umbraco.Web/Cache/PageCacheRefresher.cs index e884c9b3b8..d9bb7e9b88 100644 --- a/src/Umbraco.Web/Cache/PageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/PageCacheRefresher.cs @@ -50,8 +50,9 @@ namespace Umbraco.Web.Cache /// public override void RefreshAll() { - content.Instance.RefreshContentFromDatabase(); - XmlPublishedContent.ClearRequest(); + if (Suspendable.PageCacheRefresher.CanRefreshDocumentCacheFromDatabase) + content.Instance.RefreshContentFromDatabase(); + ClearCaches(); base.RefreshAll(); } @@ -61,11 +62,9 @@ namespace Umbraco.Web.Cache /// The id. public override void Refresh(int id) { - ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); - content.Instance.UpdateDocumentCache(id); - XmlPublishedContent.ClearRequest(); - DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); - DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); + if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache) + content.Instance.UpdateDocumentCache(id); + ClearCaches(); base.Refresh(id); } @@ -75,35 +74,35 @@ namespace Umbraco.Web.Cache /// The id. public override void Remove(int id) { - ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); - content.Instance.ClearDocumentCache(id, false); - XmlPublishedContent.ClearRequest(); - DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); - DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); - ClearAllIsolatedCacheByEntityType(); + if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache) + content.Instance.ClearDocumentCache(id, false); + ClearCaches(); base.Remove(id); } public override void Refresh(IContent instance) { - ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); - content.Instance.UpdateDocumentCache(new Document(instance)); - XmlPublishedContent.ClearRequest(); - DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); - DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); - ClearAllIsolatedCacheByEntityType(); + if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache) + content.Instance.UpdateDocumentCache(new Document(instance)); + ClearCaches(); base.Refresh(instance); } public override void Remove(IContent instance) + { + if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache) + content.Instance.ClearDocumentCache(new Document(instance), false); + ClearCaches(); + base.Remove(instance); + } + + private void ClearCaches() { ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); - content.Instance.ClearDocumentCache(new Document(instance), false); XmlPublishedContent.ClearRequest(); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); ClearAllIsolatedCacheByEntityType(); - base.Remove(instance); } } } diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index ab41246753..3b185ef4b1 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -286,10 +286,11 @@ namespace Umbraco.Web.Editors GetMaxRequestLength() }, {"keepUserLoggedIn", UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn}, + {"usernameIsEmail", UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail}, {"cssPath", IOHelper.ResolveUrl(SystemDirectories.Css).TrimEnd('/')}, {"allowPasswordReset", UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset}, {"loginBackgroundImage", UmbracoConfig.For.UmbracoSettings().Content.LoginBackgroundImage}, - {"emailServerConfigured", GlobalSettings.HasSmtpServerConfigured(_httpContext.Request.ApplicationPath)}, + {"showUserInvite", EmailSender.CanSendRequiredEmail}, } }, { diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index f0e67ac14b..8581e28d6d 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -182,6 +182,15 @@ namespace Umbraco.Web.Editors : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); } + public HttpResponseMessage PostRenameContainer(int id, string name) + { + var result = Services.ContentTypeService.RenameContentTypeContainer(id, name, Security.CurrentUser.Id); + + return result + ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id + : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); + } + public DocumentTypeDisplay PostSave(DocumentTypeSave contentTypeSave) { var savedCt = PerformPostSave( diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index 522c1f2b48..d42b158cfb 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -22,8 +22,8 @@ using System.Net.Http; using System.Text; namespace Umbraco.Web.Editors -{ - +{ + /// /// The API controller used for editing data types /// @@ -75,8 +75,8 @@ namespace Umbraco.Web.Editors if (foundType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); - } - + } + Services.DataTypeService.Delete(foundType, Security.CurrentUser.Id); return Request.CreateResponse(HttpStatusCode.OK); @@ -111,17 +111,17 @@ namespace Umbraco.Web.Editors /// public DataTypeDisplay PostCreateCustomListView(string contentTypeAlias) { - var dt = Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); - - //if it doesnt exist yet, we will create it. + var dt = Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); + + //if it doesnt exist yet, we will create it. if (dt == null) { - dt = new DataTypeDefinition( Constants.PropertyEditors.ListViewAlias ); + dt = new DataTypeDefinition(Constants.PropertyEditors.ListViewAlias); dt.Name = Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; Services.DataTypeService.Save(dt); } - return Mapper.Map(dt); + return Mapper.Map(dt); } /// @@ -161,7 +161,7 @@ namespace Umbraco.Web.Editors } //these are new pre-values, so just return the field editors with default values - return Mapper.Map>(propEd); + return Mapper.Map>(propEd); } /// @@ -243,31 +243,40 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.NotFound); } - var result = Services.DataTypeService.Move(toMove, move.ParentId); - if (result.Success) - { - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); - return response; - } - - switch (result.Result.StatusType) - { - case MoveOperationStatusType.FailedParentNotFound: - return Request.CreateResponse(HttpStatusCode.NotFound); + var result = Services.DataTypeService.Move(toMove, move.ParentId); + if (result.Success) + { + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); + return response; + } + + switch (result.Result.StatusType) + { + case MoveOperationStatusType.FailedParentNotFound: + return Request.CreateResponse(HttpStatusCode.NotFound); case MoveOperationStatusType.FailedCancelledByEvent: //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - case MoveOperationStatusType.FailedNotAllowedByPath: + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + case MoveOperationStatusType.FailedNotAllowedByPath: var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); return Request.CreateValidationErrorResponse(notificationModel); - default: - throw new ArgumentOutOfRangeException(); + default: + throw new ArgumentOutOfRangeException(); } } + public HttpResponseMessage PostRenameContainer(int id, string name) + { + var result = Services.ContentTypeService.RenameDataTypeContainer(id, name, Security.CurrentUser.Id); + + return result + ? Request.CreateResponse(HttpStatusCode.OK, result.Result) + : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); + } + #region ReadOnly actions to return basic data - allow access for: content ,media, members, settings, developer /// /// Gets the content json for all data types @@ -308,7 +317,7 @@ namespace Umbraco.Web.Editors foreach (var dataType in dataTypes) { var propertyEditor = propertyEditors.SingleOrDefault(x => x.Alias == dataType.Alias); - if(propertyEditor != null) + if (propertyEditor != null) dataType.HasPrevalues = propertyEditor.PreValueEditor.Fields.Any(); ; } @@ -331,8 +340,8 @@ namespace Umbraco.Web.Editors Constants.Applications.Settings, Constants.Applications.Developer)] public IDictionary> GetGroupedPropertyEditors() { - var datatypes = new List(); - + var datatypes = new List(); + var propertyEditors = PropertyEditorResolver.Current.PropertyEditors; foreach (var propertyEditor in propertyEditors) { diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index 2ad7aa3206..43312fb7c3 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -170,6 +170,16 @@ namespace Umbraco.Web.Editors : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); } + public HttpResponseMessage PostRenameContainer(int id, string name) + { + + var result = Services.ContentTypeService.RenameMediaTypeContainer(id, name, Security.CurrentUser.Id); + + return result + ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id + : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); + } + public MediaTypeDisplay PostSave(MediaTypeSave contentTypeSave) { var savedCt = PerformPostSave( diff --git a/src/Umbraco.Web/Editors/PasswordChanger.cs b/src/Umbraco.Web/Editors/PasswordChanger.cs index a6edd1bdd0..88e92c0ad2 100644 --- a/src/Umbraco.Web/Editors/PasswordChanger.cs +++ b/src/Umbraco.Web/Editors/PasswordChanger.cs @@ -98,8 +98,8 @@ namespace Umbraco.Web.Editors _logger.Warn(string.Format("Could not reset user password {0}", errors)); return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not reset password, errors: " + errors, new[] { "resetPassword" }) }); } - - return Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass }); + + return Attempt.Succeed(new PasswordChangedModel()); } //we're not resetting it so we need to try to change it. diff --git a/src/Umbraco.Web/Editors/UserGroupsController.cs b/src/Umbraco.Web/Editors/UserGroupsController.cs index 33b1d9dc08..66011e01b2 100644 --- a/src/Umbraco.Web/Editors/UserGroupsController.cs +++ b/src/Umbraco.Web/Editors/UserGroupsController.cs @@ -133,7 +133,10 @@ namespace Umbraco.Web.Editors [UserGroupAuthorization("userGroupIds")] public HttpResponseMessage PostDeleteUserGroups([FromUri] int[] userGroupIds) { - var userGroups = Services.UserService.GetAllUserGroups(userGroupIds).ToArray(); + var userGroups = Services.UserService.GetAllUserGroups(userGroupIds) + //never delete the admin group or translators group + .Where(x => x.Alias != Constants.Security.AdminGroupAlias && x.Alias != Constants.Security.TranslatorGroupAlias) + .ToArray(); foreach (var userGroup in userGroups) { Services.UserService.DeleteUserGroup(userGroup); diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 37bcd97214..b6852a9286 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -189,7 +189,8 @@ namespace Umbraco.Web.Editors { throw new HttpResponseException(HttpStatusCode.NotFound); } - return Mapper.Map(user); + var result = Mapper.Map(user); + return result; } /// @@ -265,13 +266,18 @@ namespace Umbraco.Web.Editors { throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); } - - var existing = Services.UserService.GetByEmail(userSave.Email); - if (existing != null) + + if (UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail) { - ModelState.AddModelError("Email", "A user with the email already exists"); - throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + //ensure they are the same if we're using it + userSave.Username = userSave.Email; } + else + { + //first validate the username if were showing it + CheckUniqueUsername(userSave.Username, null); + } + CheckUniqueEmail(userSave.Email, null); //Perform authorization here to see if the current user can actually save this user with the info being requested var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService); @@ -283,7 +289,7 @@ namespace Umbraco.Web.Editors //we want to create the user with the UserManager, this ensures the 'empty' (special) password //format is applied without us having to duplicate that logic - var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Email, userSave.Email, GlobalSettings.DefaultUILanguage); + var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Username, userSave.Email, GlobalSettings.DefaultUILanguage); identityUser.Name = userSave.Name; var created = await UserManager.CreateAsync(identityUser); @@ -345,21 +351,26 @@ namespace Umbraco.Web.Editors { throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); } - - var hasSmtp = GlobalSettings.HasSmtpServerConfigured(RequestContext.VirtualPathRoot); - if (hasSmtp == false) + + if (EmailSender.CanSendRequiredEmail == false) { throw new HttpResponseException( Request.CreateNotificationValidationErrorResponse("No Email server is configured")); } - var user = Services.UserService.GetByEmail(userSave.Email); - if (user != null && (user.LastLoginDate != default(DateTime) || user.EmailConfirmedDate.HasValue)) + IUser user; + if (UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail) { - ModelState.AddModelError("Email", "A user with the email already exists"); - throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + //ensure it's the same + userSave.Username = userSave.Email; } - + else + { + //first validate the username if we're showing it + user = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default(DateTime) || u.EmailConfirmedDate.HasValue); + } + user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default(DateTime) || u.EmailConfirmedDate.HasValue); + //Perform authorization here to see if the current user can actually save this user with the info being requested var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService); var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, user, null, null, userSave.UserGroups); @@ -372,7 +383,7 @@ namespace Umbraco.Web.Editors { //we want to create the user with the UserManager, this ensures the 'empty' (special) password //format is applied without us having to duplicate that logic - var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Email, userSave.Email, GlobalSettings.DefaultUILanguage); + var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Username, userSave.Email, GlobalSettings.DefaultUILanguage); identityUser.Name = userSave.Name; var created = await UserManager.CreateAsync(identityUser); @@ -402,7 +413,31 @@ namespace Umbraco.Web.Editors return display; } - + + private IUser CheckUniqueEmail(string email, Func extraCheck) + { + var user = Services.UserService.GetByEmail(email); + if (user != null && (extraCheck == null || extraCheck(user))) + { + ModelState.AddModelError("Email", "A user with the email already exists"); + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + return user; + } + + private IUser CheckUniqueUsername(string username, Func extraCheck) + { + var user = Services.UserService.GetByUsername(username); + if (user != null && (extraCheck == null || extraCheck(user))) + { + ModelState.AddModelError( + UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail ? "Email" : "Username", + "A user with the username already exists"); + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + return user; + } + private HttpContextBase EnsureHttpContext() { var attempt = this.TryGetHttpContext(); @@ -442,12 +477,15 @@ namespace Umbraco.Web.Editors UserExtensions.GetUserCulture(to.Language, Services.TextService), new[] { userDisplay.Name, from, message, inviteUri.ToString() }); - await UserManager.EmailService.SendAsync(new IdentityMessage - { - Body = emailBody, - Destination = userDisplay.Email, - Subject = emailSubject - }); + await UserManager.EmailService.SendAsync( + //send the special UmbracoEmailMessage which configures it's own sender + //to allow for events to handle sending the message if no smtp is configured + new UmbracoEmailMessage(new EmailSender(true)) + { + Body = emailBody, + Destination = userDisplay.Email, + Subject = emailSubject + }); } @@ -516,8 +554,7 @@ namespace Umbraco.Web.Editors { userSave.Username = userSave.Email; } - - var resetPasswordValue = string.Empty; + if (userSave.ChangePassword != null) { var passwordChanger = new PasswordChanger(Logger, Services.UserService); @@ -525,9 +562,6 @@ namespace Umbraco.Web.Editors var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, found, userSave.ChangePassword, UserManager); if (passwordChangeResult.Success) { - //depending on how the provider is configured, the password may be reset so let's store that for later - resetPasswordValue = passwordChangeResult.Result.ResetPassword; - //need to re-get the user found = Services.UserService.GetUserById(intId.Result); } @@ -551,11 +585,7 @@ namespace Umbraco.Web.Editors Services.UserService.Save(user); var display = Mapper.Map(user); - - //re-map the password reset value (if any) - if (resetPasswordValue.IsNullOrWhiteSpace() == false) - display.ResetPasswordValue = resetPasswordValue; - + display.AddSuccessNotification(Services.TextService.Localize("speechBubbles/operationSavedHeader"), Services.TextService.Localize("speechBubbles/editUserSaved")); return display; } diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs b/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs index 6e26849dc8..96762f540d 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.HealthCheck /// /// Each instance scoped to the lifespan of the http request /// - internal class HealthCheckResolver : LazyManyObjectsResolverBase, IHealthCheckResolver + public class HealthCheckResolver : LazyManyObjectsResolverBase, IHealthCheckResolver { public HealthCheckResolver(ILogger logger, Func> lazyTypeList) : base(new HealthCheckServiceProvider(), logger, lazyTypeList, ObjectLifetimeScope.Application) diff --git a/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs index c43fb0d3a1..998525c96a 100644 --- a/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs @@ -67,7 +67,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods var subject = _textService.Localize("healthcheck/scheduledHealthCheckEmailSubject"); - using (var client = new SmtpClient()) + var mailSender = new EmailSender(); using (var mailMessage = new MailMessage(UmbracoConfig.For.UmbracoSettings().Content.NotificationEmailAddress, RecipientEmail, string.IsNullOrEmpty(subject) ? "Umbraco Health Check Status" : subject, @@ -77,14 +77,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods && message.Contains("<") && message.Contains("The text with text line breaks replaced with html linebreaks (
) public string ReplaceLineBreaksForHtml(string text) { - return text.Replace("\n", "
\n"); + return text.Replace("\r\n", @"
").Replace("\n", @"
").Replace("\r", @"
"); } public HtmlString StripHtmlTags(string html, params string[] tags) { var doc = new HtmlDocument(); doc.LoadHtml("

" + html + "

"); + var targets = new List(); var nodes = doc.DocumentNode.FirstChild.SelectNodes(".//*"); @@ -55,7 +58,7 @@ namespace Umbraco.Web { return new HtmlString(html); } - return new HtmlString(doc.DocumentNode.FirstChild.InnerHtml); + return new HtmlString(doc.DocumentNode.FirstChild.InnerHtml.Replace(" ", " ")); } internal string Join(string seperator, params object[] args) @@ -85,6 +88,8 @@ namespace Umbraco.Web public IHtmlString Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent) { + const string hellip = "…"; + using (var outputms = new MemoryStream()) { using (var outputtw = new StreamWriter(outputms)) @@ -106,7 +111,7 @@ namespace Umbraco.Web isTagClose = false; int ic = 0, - currentLength = 0, + //currentLength = 0, currentTextLength = 0; string currentTag = string.Empty, @@ -141,6 +146,10 @@ namespace Umbraco.Web { string thisTag = tagStack.Pop(); outputtw.Write(""); + if (treatTagsAsContent) + { + currentTextLength++; + } } if (!isTagClose && currentTag.Length > 0) { @@ -148,6 +157,10 @@ namespace Umbraco.Web { tagStack.Push(currentTag); outputtw.Write("<" + currentTag); + if (treatTagsAsContent) + { + currentTextLength++; + } if (!string.IsNullOrEmpty(tagContents)) { if (tagContents.EndsWith("/")) @@ -205,7 +218,7 @@ namespace Umbraco.Web { var charToWrite = (char)ic; outputtw.Write(charToWrite); - currentLength++; + //currentLength++; } } @@ -221,7 +234,7 @@ namespace Umbraco.Web // Reached truncate limit. if (addElipsis) { - outputtw.Write("…"); + outputtw.Write(hellip); } lengthReached = true; } @@ -235,10 +248,59 @@ namespace Umbraco.Web outputms.Position = 0; using (TextReader outputtr = new StreamReader(outputms)) { - return new HtmlString(outputtr.ReadToEnd().Replace(" ", " ").Trim()); + string result = string.Empty; + + string firstTrim = outputtr.ReadToEnd().Replace(" ", " ").Trim(); + + //Check to see if there is an empty char between the hellip and the output string + //if there is, remove it + if (string.IsNullOrWhiteSpace(firstTrim) == false) + { + result = firstTrim[firstTrim.Length - hellip.Length - 1] == ' ' ? firstTrim.Remove(firstTrim.Length - hellip.Length - 1, 1) : firstTrim; + } + return new HtmlString(result); } } } } + + /// + /// Returns the length of the words from a html block + /// + /// Html text + /// Amount of words you would like to measure + /// + /// + public int WordsToLength(string html, int words) + { + HtmlDocument doc = new HtmlDocument(); + doc.LoadHtml(html); + + int wordCount = 0, + length = 0, + maxWords = words; + + html = StripHtmlTags(html, null).ToString(); + + while (length < html.Length) + { + // Check to see if the current wordCount reached the maxWords allowed + if (wordCount.Equals(maxWords)) break; + // Check if current char is part of a word + while (length < html.Length && char.IsWhiteSpace(html[length]) == false) + { + length++; + } + + wordCount++; + + // Skip whitespace until the next word + while (length < html.Length && char.IsWhiteSpace(html[length]) && wordCount.Equals(maxWords) == false) + { + length++; + } + } + return length; + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/Controllers/InstallController.cs b/src/Umbraco.Web/Install/Controllers/InstallController.cs index 360e35efda..7e3336e1e8 100644 --- a/src/Umbraco.Web/Install/Controllers/InstallController.cs +++ b/src/Umbraco.Web/Install/Controllers/InstallController.cs @@ -47,6 +47,8 @@ namespace Umbraco.Web.Install.Controllers // Update ClientDependency version var clientDependencyConfig = new ClientDependencyConfiguration(ApplicationContext.Current.ProfilingLogger.Logger); var clientDependencyUpdated = clientDependencyConfig.IncreaseVersionNumber(); + // Delete ClientDependency temp directories to make sure we get fresh caches + var clientDependencyTempFilesDeleted = clientDependencyConfig.ClearTempFiles(HttpContext); var result = _umbracoContext.Security.ValidateCurrentUser(false); diff --git a/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs b/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs index 06895ccc68..368067814d 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; +using Umbraco.Core; +using Umbraco.Core.Configuration; namespace Umbraco.Web.Models.ContentEditing { @@ -18,7 +20,10 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "email", IsRequired = true)] [Required] [EmailAddress] - public string Email { get; set; } + public string Email { get; set; } + + [DataMember(Name = "username")] + public string Username { get; set; } [DataMember(Name = "message")] public string Message { get; set; } @@ -26,7 +31,10 @@ namespace Umbraco.Web.Models.ContentEditing public IEnumerable Validate(ValidationContext validationContext) { if (UserGroups.Any() == false) - yield return new ValidationResult("A user must be assigned to at least one group", new[] { "UserGroups" }); + yield return new ValidationResult("A user must be assigned to at least one group", new[] { "UserGroups" }); + + if (UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail == false && Username.IsNullOrWhiteSpace()) + yield return new ValidationResult("A username cannot be empty", new[] { "Username" }); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 341a0a6fb3..00ff0c6018 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -249,7 +249,7 @@ namespace Umbraco.Web.Models.Mapping user.CalculateMediaStartNodeIds(applicationContext.Services.EntityService), applicationContext.Services.TextService, applicationContext.Services.EntityService, - UmbracoObjectTypes.Document, + UmbracoObjectTypes.Media, "media/mediaRoot"))) .ForMember( detail => detail.StartContentIds, @@ -337,7 +337,7 @@ namespace Umbraco.Web.Models.Mapping } else { - var builtIns = new[] { Constants.Security.AdminGroupAlias, "writer", "editor", "translator" }; + var builtIns = new[] { Constants.Security.AdminGroupAlias, "writer", "editor", Constants.Security.TranslatorGroupAlias }; var foundBuiltIn = groups.FirstOrDefault(x => builtIns.Contains(x.Alias)); if (foundBuiltIn != null) { diff --git a/src/Umbraco.Web/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainHelper.cs index ab2c14f009..88488db83f 100644 --- a/src/Umbraco.Web/Routing/DomainHelper.cs +++ b/src/Umbraco.Web/Routing/DomainHelper.cs @@ -269,7 +269,7 @@ namespace Umbraco.Web.Routing /// Eg the relative part of /foo/bar/nil to domain example.com/foo is /bar/nil. public static string PathRelativeToDomain(Uri domainUri, string path) { - return Uri.EscapeUriString(path).Substring(domainUri.AbsolutePath.Length).EnsureStartsWith('/'); + return path.Substring(domainUri.GetAbsolutePathDecoded().Length).EnsureStartsWith('/'); } #endregion diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index 73b28ae669..46a756e0ad 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -35,6 +35,9 @@ namespace Umbraco.Web.Scheduling { if (_appContext == null) return true; // repeat... + if (Suspendable.ScheduledPublishing.CanRun == false) + return true; // repeat, later + switch (_appContext.GetCurrentServerRole()) { case ServerRole.Slave: diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineEvents.cs index 7fbbf29b89..e018bac214 100644 --- a/src/Umbraco.Web/Search/ExamineEvents.cs +++ b/src/Umbraco.Web/Search/ExamineEvents.cs @@ -24,7 +24,6 @@ namespace Umbraco.Web.Search ///
public sealed class ExamineEvents : ApplicationEventHandler { - /// /// Once the application has started we should bind to all events and initialize the providers. /// @@ -32,9 +31,9 @@ namespace Umbraco.Web.Search /// /// /// We need to do this on the Started event as to guarantee that all resolvers are setup properly. - /// + /// protected override void ApplicationStarted(UmbracoApplicationBase httpApplication, ApplicationContext applicationContext) - { + { LogHelper.Info("Initializing Examine and binding to business logic events"); var registeredProviders = ExamineManager.Instance.IndexProviderCollection @@ -46,14 +45,14 @@ namespace Umbraco.Web.Search if (registeredProviders == 0) return; - //Bind to distributed cache events - this ensures that this logic occurs on ALL servers that are taking part + //Bind to distributed cache events - this ensures that this logic occurs on ALL servers that are taking part // in a load balanced environment. CacheRefresherBase.CacheUpdated += UnpublishedPageCacheRefresherCacheUpdated; CacheRefresherBase.CacheUpdated += PublishedPageCacheRefresherCacheUpdated; CacheRefresherBase.CacheUpdated += MediaCacheRefresherCacheUpdated; CacheRefresherBase.CacheUpdated += MemberCacheRefresherCacheUpdated; CacheRefresherBase.CacheUpdated += ContentTypeCacheRefresherCacheUpdated; - + var contentIndexer = ExamineManager.Instance.IndexProviderCollection[Constants.Examine.InternalIndexer] as UmbracoContentIndexer; if (contentIndexer != null) { @@ -77,6 +76,9 @@ namespace Umbraco.Web.Search /// static void ContentTypeCacheRefresherCacheUpdated(ContentTypeCacheRefresher sender, CacheRefresherEventArgs e) { + if (Suspendable.ExamineEvents.CanIndex == false) + return; + var indexersToUpdated = ExamineManager.Instance.IndexProviderCollection.OfType(); foreach (var provider in indexersToUpdated) { @@ -114,7 +116,7 @@ namespace Umbraco.Web.Search } } - //TODO: We need to update Examine to support re-indexing multiple items at once instead of one by one which will speed up + //TODO: We need to update Examine to support re-indexing multiple items at once instead of one by one which will speed up // the re-indexing process, we don't want to revert to rebuilding the whole thing! if (contentTypesChanged.Count > 0) @@ -129,8 +131,8 @@ namespace Umbraco.Web.Search { ReIndexForContent(contentItem, contentItem.HasPublishedVersion && contentItem.Trashed == false); } - } - } + } + } } if (mediaTypesChanged.Count > 0) { @@ -163,11 +165,14 @@ namespace Umbraco.Web.Search } } } - + } static void MemberCacheRefresherCacheUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs e) { + if (Suspendable.ExamineEvents.CanIndex == false) + return; + switch (e.MessageType) { case MessageType.RefreshById: @@ -215,6 +220,9 @@ namespace Umbraco.Web.Search /// static void MediaCacheRefresherCacheUpdated(MediaCacheRefresher sender, CacheRefresherEventArgs e) { + if (Suspendable.ExamineEvents.CanIndex == false) + return; + switch (e.MessageType) { case MessageType.RefreshById: @@ -252,13 +260,13 @@ namespace Umbraco.Web.Search if (media1 != null) { ReIndexForMedia(media1, media1.Trashed == false); - } + } break; case MediaCacheRefresher.OperationType.Trashed: - + //keep if trashed for indexes supporting unpublished //(delete the index from all indexes not supporting unpublished content) - + DeleteIndexForEntity(payload.Id, true); //We then need to re-index this item for all indexes supporting unpublished content @@ -272,20 +280,20 @@ namespace Umbraco.Web.Search case MediaCacheRefresher.OperationType.Deleted: //permanently remove from all indexes - + DeleteIndexForEntity(payload.Id, false); break; default: throw new ArgumentOutOfRangeException(); - } - } + } + } } break; - case MessageType.RefreshByInstance: - case MessageType.RemoveByInstance: - case MessageType.RefreshAll: + case MessageType.RefreshByInstance: + case MessageType.RemoveByInstance: + case MessageType.RefreshAll: default: //We don't support these, these message types will not fire for media break; @@ -302,6 +310,9 @@ namespace Umbraco.Web.Search /// static void PublishedPageCacheRefresherCacheUpdated(PageCacheRefresher sender, CacheRefresherEventArgs e) { + if (Suspendable.ExamineEvents.CanIndex == false) + return; + switch (e.MessageType) { case MessageType.RefreshById: @@ -312,8 +323,8 @@ namespace Umbraco.Web.Search } break; case MessageType.RemoveById: - - //This is triggered when the item has been unpublished or trashed (which also performs an unpublish). + + //This is triggered when the item has been unpublished or trashed (which also performs an unpublish). var c2 = ApplicationContext.Current.Services.ContentService.GetById((int)e.MessageObject); if (c2 != null) @@ -368,6 +379,9 @@ namespace Umbraco.Web.Search /// static void UnpublishedPageCacheRefresherCacheUpdated(UnpublishedPageCacheRefresher sender, CacheRefresherEventArgs e) { + if (Suspendable.ExamineEvents.CanIndex == false) + return; + switch (e.MessageType) { case MessageType.RefreshById: @@ -378,9 +392,9 @@ namespace Umbraco.Web.Search } break; case MessageType.RemoveById: - + // This is triggered when the item is permanently deleted - + DeleteIndexForEntity((int)e.MessageObject, false); break; case MessageType.RefreshByInstance: @@ -399,7 +413,7 @@ namespace Umbraco.Web.Search { DeleteIndexForEntity(c4.Id, false); } - break; + break; case MessageType.RefreshByJson: var jsonPayloads = UnpublishedPageCacheRefresher.DeserializeFromJsonPayload((string)e.MessageObject); @@ -409,29 +423,28 @@ namespace Umbraco.Web.Search { switch (payload.Operation) { - case UnpublishedPageCacheRefresher.OperationType.Deleted: + case UnpublishedPageCacheRefresher.OperationType.Deleted: //permanently remove from all indexes - + DeleteIndexForEntity(payload.Id, false); break; default: throw new ArgumentOutOfRangeException(); - } - } + } + } } break; - case MessageType.RefreshAll: + case MessageType.RefreshAll: default: //We don't support these, these message types will not fire for unpublished content break; } } - private static void ReIndexForMember(IMember member) { ExamineManager.Instance.ReIndexNode( @@ -447,7 +460,7 @@ namespace Umbraco.Web.Search ///
/// /// - + private static void IndexerDocumentWriting(object sender, DocumentWritingEventArgs e) { if (e.Fields.Keys.Contains("nodeName")) @@ -463,7 +476,7 @@ namespace Umbraco.Web.Search )); } } - + private static void ReIndexForMedia(IMedia sender, bool isMediaPublished) { var xml = sender.ToXml(); @@ -497,7 +510,7 @@ namespace Umbraco.Web.Search //if keepIfUnpublished == true then only delete this item from indexes not supporting unpublished content, // otherwise if keepIfUnpublished == false then remove from all indexes - + .Where(x => keepIfUnpublished == false || x.SupportUnpublishedContent == false) .Where(x => x.EnableDefaultEventHandler)); } @@ -518,7 +531,7 @@ namespace Umbraco.Web.Search ExamineManager.Instance.ReIndexNode( xml, IndexTypes.Content, ExamineManager.Instance.IndexProviderCollection.OfType() - + //Index this item for all indexers if the content is published, otherwise if the item is not published // then only index this for indexers supporting unpublished content @@ -531,10 +544,10 @@ namespace Umbraco.Web.Search ///
/// /// true if data is going to be returned from cache - /// + /// [Obsolete("This method is no longer used and will be removed from the core in future versions, the cacheOnly parameter has no effect. Use the other ToXDocument overload instead")] public static XDocument ToXDocument(Content node, bool cacheOnly) - { + { return ToXDocument(node); } @@ -542,7 +555,7 @@ namespace Umbraco.Web.Search /// Converts a content node to Xml ///
/// - /// + /// private static XDocument ToXDocument(Content node) { if (TypeHelper.IsTypeAssignableFrom(node)) @@ -561,7 +574,7 @@ namespace Umbraco.Web.Search if (xNode.Attributes["nodeTypeAlias"] == null) { - //we'll add the nodeTypeAlias ourselves + //we'll add the nodeTypeAlias ourselves XmlAttribute d = xDoc.CreateAttribute("nodeTypeAlias"); d.Value = node.ContentType.Alias; xNode.Attributes.Append(d); @@ -569,6 +582,5 @@ namespace Umbraco.Web.Search return new XDocument(ExamineXmlExtensions.ToXElement(xNode)); } - } } \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs index 69c926db3e..9953eea664 100644 --- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs @@ -90,7 +90,8 @@ namespace Umbraco.Web.Security.Identity appContext.Services.UserService, appContext.Services.EntityService, appContext.Services.ExternalLoginService, - userMembershipProvider)); + userMembershipProvider, + UmbracoConfig.For.UmbracoSettings().Content)); app.SetBackOfficeUserManagerType(); @@ -119,7 +120,8 @@ namespace Umbraco.Web.Security.Identity (options, owinContext) => BackOfficeUserManager.Create( options, customUserStore, - userMembershipProvider)); + userMembershipProvider, + UmbracoConfig.For.UmbracoSettings().Content)); app.SetBackOfficeUserManagerType(); diff --git a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs index 7034101a83..fdebf78480 100644 --- a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs @@ -42,10 +42,36 @@ namespace Umbraco.Web.Security.Providers return entity.AsConcreteMembershipUser(Name, true); } + private bool _allowManuallyChangingPassword = false; + private bool _enablePasswordReset = false; + + /// + /// Indicates whether the membership provider is configured to allow users to reset their passwords. + /// + /// + /// true if the membership provider supports password reset; otherwise, false. The default is FALSE for users. + public override bool EnablePasswordReset + { + get { return _enablePasswordReset; } + } + + /// + /// For backwards compatibility, this provider supports this option by default it is FALSE for users + /// + public override bool AllowManuallyChangingPassword + { + get { return _allowManuallyChangingPassword; } + } + public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { base.Initialize(name, config); + if (config == null) { throw new ArgumentNullException("config"); } + + _allowManuallyChangingPassword = config.GetValue("allowManuallyChangingPassword", false); + _enablePasswordReset = config.GetValue("enablePasswordReset", false); + // test for membertype (if not specified, choose the first member type available) // We'll support both names for legacy reasons: defaultUserTypeAlias & defaultUserGroupAlias diff --git a/src/Umbraco.Web/Suspendable.cs b/src/Umbraco.Web/Suspendable.cs new file mode 100644 index 0000000000..db4ef53485 --- /dev/null +++ b/src/Umbraco.Web/Suspendable.cs @@ -0,0 +1,116 @@ +using System; +using System.Diagnostics; +using Examine; +using Examine.Providers; +using Umbraco.Core; +using Umbraco.Web.Cache; + +namespace Umbraco.Web +{ + internal static class Suspendable + { + public static class PageCacheRefresher + { + private static bool _tried, _suspended; + + public static bool CanRefreshDocumentCacheFromDatabase + { + get + { + // trying a full refresh + if (_suspended == false) return true; + _tried = true; // remember we tried + return false; + } + } + + public static bool CanUpdateDocumentCache + { + get + { + // trying a partial update + // ok if not suspended, or if we haven't done a full already + return _suspended == false || _tried == false; + } + } + + public static void SuspendDocumentCache() + { + ApplicationContext.Current.ProfilingLogger.Logger.Info(typeof (PageCacheRefresher), "Suspend document cache."); + _suspended = true; + } + + public static void ResumeDocumentCache() + { + _suspended = false; + + ApplicationContext.Current.ProfilingLogger.Logger.Info(typeof (PageCacheRefresher), string.Format("Resume document cache (reload:{0}).", _tried ? "true" : "false")); + + if (_tried == false) return; + _tried = false; + + var pageRefresher = CacheRefreshersResolver.Current.GetById(new Guid(DistributedCache.PageCacheRefresherId)); + pageRefresher.RefreshAll(); + } + } + + public static class ExamineEvents + { + private static bool _tried, _suspended; + + public static bool CanIndex + { + get + { + if (_suspended == false) return true; + _tried = true; // remember we tried + return false; + } + } + + public static void SuspendIndexers() + { + ApplicationContext.Current.ProfilingLogger.Logger.Info(typeof (ExamineEvents), "Suspend indexers."); + _suspended = true; + } + + public static void ResumeIndexers() + { + _suspended = false; + + ApplicationContext.Current.ProfilingLogger.Logger.Info(typeof (ExamineEvents), string.Format("Resume indexers (rebuild:{0}).", _tried ? "true" : "false")); + + if (_tried == false) return; + _tried = false; + + // fixme - could we fork this on a background thread? + foreach (BaseIndexProvider indexer in ExamineManager.Instance.IndexProviderCollection) + { + indexer.RebuildIndex(); + } + } + } + + public static class ScheduledPublishing + { + private static bool _suspended; + + public static bool CanRun + { + get { return _suspended == false; } + } + + public static void Suspend() + { + ApplicationContext.Current.ProfilingLogger.Logger.Info(typeof (ScheduledPublishing), "Suspend scheduled publishing."); + _suspended = true; + } + + public static void Resume() + { + ApplicationContext.Current.ProfilingLogger.Logger.Info(typeof (ScheduledPublishing), "Resume scheduled publishing."); + _suspended = false; + } + } + } +} diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 9b3a6e82f8..ea644f5dc8 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -41,22 +41,8 @@ namespace Umbraco.Web.Trees [SearchableTree("searchResultFormatter", "configureContentResult")] public class ContentTreeController : ContentTreeControllerBase, ISearchableTree { - private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); - - protected override TreeNode CreateRootNode(FormDataCollection queryStrings) - { - var node = base.CreateRootNode(queryStrings); - - // if the user's start node is not default, then ensure the root doesn't have a menu - if (UserStartNodes.Contains(Constants.System.Root) == false) - { - node.MenuUrl = ""; - } - node.Name = ui.Text("sections", Constants.Trees.Content); - return node; - } - + protected override int RecycleBinId { get { return Constants.System.RecycleBinContent; } @@ -123,14 +109,17 @@ namespace Umbraco.Web.Trees } protected override MenuItemCollection PerformGetMenuForNode(string id, FormDataCollection queryStrings) - { + { if (id == Constants.System.Root.ToInvariantString()) { var menu = new MenuItemCollection(); - // if the user's start node is not the root then ensure the root menu is empty/doesn't exist + // if the user's start node is not the root then the only menu item to display is refresh if (UserStartNodes.Contains(Constants.System.Root) == false) { + menu.Items.Add( + Services.TextService.Localize(string.Concat("actions/", ActionRefresh.Instance.Alias)), + true); return menu; } @@ -174,6 +163,16 @@ namespace Umbraco.Web.Trees { throw new HttpResponseException(HttpStatusCode.NotFound); } + + //if the user has no path access for this node, all they can do is refresh + if (Security.CurrentUser.HasPathAccess(item, Services.EntityService, RecycleBinId) == false) + { + var menu = new MenuItemCollection(); + menu.Items.Add( + Services.TextService.Localize(string.Concat("actions/", ActionRefresh.Instance.Alias)), + true); + return menu; + } var nodeMenu = GetAllNodeMenuItems(item); var allowedMenuItems = GetAllowedUserMenuItemsForNode(item); @@ -210,17 +209,7 @@ namespace Umbraco.Web.Trees protected override bool HasPathAccess(string id, FormDataCollection queryStrings) { var entity = GetEntityFromId(id); - if (entity == null) - { - return false; - } - - var content = Services.ContentService.GetById(entity.Id); - if (content == null) - { - return false; - } - return Security.CurrentUser.HasPathAccess(content, Services.EntityService); + return HasPathAccess(entity, queryStrings); } /// diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index fa18e703bd..8fabc1677e 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Net; using System.Net.Http; @@ -52,8 +54,48 @@ namespace Umbraco.Web.Trees } #endregion + + /// + /// Ensure the noAccess metadata is applied for the root node if in dialog mode and the user doesn't have path access to it + /// + /// + /// + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var node = base.CreateRootNode(queryStrings); + + if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false) + { + node.AdditionalData["noAccess"] = true; + } + + return node; + } protected abstract TreeNode GetSingleTreeNode(IUmbracoEntity e, string parentId, FormDataCollection queryStrings); + + /// + /// Returns a for the and + /// attaches some meta data to the node if the user doesn't have start node access to it when in dialog mode + /// + /// + /// + /// + /// + internal TreeNode GetSingleTreeNodeWithAccessCheck(IUmbracoEntity e, string parentId, FormDataCollection queryStrings) + { + bool hasPathAccess; + var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out hasPathAccess); + if (entityIsAncestorOfStartNodes == false) + return null; + + var treeNode = GetSingleTreeNode(e, parentId, queryStrings); + if (hasPathAccess == false) + { + treeNode.AdditionalData["noAccess"] = true; + } + return treeNode; + } /// /// Returns the @@ -69,13 +111,7 @@ namespace Umbraco.Web.Trees /// Returns the user's start node for this tree /// protected abstract int[] UserStartNodes { get; } - - /// - /// Gets the tree nodes for the given id - /// - /// - /// - /// + protected virtual TreeNodeCollection PerformGetTreeNodes(string id, FormDataCollection queryStrings) { var nodes = new TreeNodeCollection(); @@ -83,9 +119,10 @@ namespace Umbraco.Web.Trees var altStartId = string.Empty; if (queryStrings.HasKey(TreeQueryStringParameters.StartNodeId)) altStartId = queryStrings.GetValue(TreeQueryStringParameters.StartNodeId); + var rootIdString = Constants.System.Root.ToString(CultureInfo.InvariantCulture); //check if a request has been made to render from a specific start node - if (string.IsNullOrEmpty(altStartId) == false && altStartId != "undefined" && altStartId != Constants.System.Root.ToString(CultureInfo.InvariantCulture)) + if (string.IsNullOrEmpty(altStartId) == false && altStartId != "undefined" && altStartId != rootIdString) { id = altStartId; @@ -111,10 +148,42 @@ namespace Umbraco.Web.Trees } } - var entities = GetChildEntities(id); - nodes.AddRange(entities.Select(entity => GetSingleTreeNode(entity, id, queryStrings)).Where(node => node != null)); + var entities = GetChildEntities(id).ToList(); + + //If we are looking up the root and there is more than one node ... + //then we want to lookup those nodes' 'site' nodes and render those so that the + //user has some context of where they are in the tree, this is generally for pickers in a dialog. + //for any node they don't have access too, we need to add some metadata + if (id == rootIdString && entities.Count > 1) + { + var siteNodeIds = new List(); + //put into array since we might modify the list + foreach (var e in entities.ToArray()) + { + var pathParts = e.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); + if (pathParts.Length < 2) + continue; // this should never happen but better to check + + int siteNodeId; + if (int.TryParse(pathParts[1], out siteNodeId) == false) + continue; + + //we'll look up this + siteNodeIds.Add(siteNodeId); + } + var siteNodes = Services.EntityService.GetAll(UmbracoObjectType, siteNodeIds.ToArray()) + .DistinctBy(e => e.Id) + .ToArray(); + + //add site nodes + nodes.AddRange(siteNodes.Select(e => GetSingleTreeNodeWithAccessCheck(e, id, queryStrings)).Where(node => node != null)); + + return nodes; + } + + nodes.AddRange(entities.Select(e => GetSingleTreeNodeWithAccessCheck(e, id, queryStrings)).Where(node => node != null)); return nodes; - } + } protected abstract MenuItemCollection PerformGetMenuForNode(string id, FormDataCollection queryStrings); @@ -154,8 +223,21 @@ namespace Umbraco.Web.Trees /// /// /// + //we should remove this in v8, it's now here for backwards compat only protected abstract bool HasPathAccess(string id, FormDataCollection queryStrings); + /// + /// Returns true or false if the current user has access to the node based on the user's allowed start node (path) access + /// + /// + /// + /// + protected bool HasPathAccess(IUmbracoEntity entity, FormDataCollection queryStrings) + { + if (entity == null) return false; + return Security.CurrentUser.HasPathAccess(entity, Services.EntityService, RecycleBinId); + } + /// /// Ensures the recycle bin is appended when required (i.e. user has access to the root and it's not in dialog mode) /// @@ -214,7 +296,7 @@ namespace Umbraco.Web.Trees /// private TreeNodeCollection GetTreeNodesInternal(string id, FormDataCollection queryStrings) { - IUmbracoEntity current = GetEntityFromId(id); + var current = GetEntityFromId(id); //before we get the children we need to see if this is a container node @@ -242,7 +324,8 @@ namespace Umbraco.Web.Trees menu.Items.Add(ui.Text("actions", "emptyTrashcan")); menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); return menu; - } + } + return PerformGetMenuForNode(id, queryStrings); } @@ -271,10 +354,11 @@ namespace Umbraco.Web.Trees internal IEnumerable GetAllowedUserMenuItemsForNode(IUmbracoEntity dd) { - var actions = global::umbraco.BusinessLogic.Actions.Action.FromString(UmbracoUser.GetPermissions(dd.Path)); - + var actions = ActionsResolver.Current.FromActionSymbols(Security.CurrentUser.GetPermissions(dd.Path, Services.UserService)) + .ToList(); + // A user is allowed to delete their own stuff - if (dd.CreatorId == UmbracoUser.Id && actions.Contains(ActionDelete.Instance) == false) + if (dd.CreatorId == Security.GetUserId() && actions.Contains(ActionDelete.Instance) == false) actions.Add(ActionDelete.Instance); return actions.Select(x => new MenuItem(x)); @@ -291,39 +375,78 @@ namespace Umbraco.Web.Trees { return allowedUserOptions.Select(x => x.Action).OfType().Any(); } - + /// - /// Get an entity via an id that can be either an integer, Guid or UDI + /// this will parse the string into either a GUID or INT /// /// /// - internal IUmbracoEntity GetEntityFromId(string id) + internal Tuple GetIdentifierFromString(string id) { - IUmbracoEntity entity; - Guid idGuid; int idInt; Udi idUdi; if (Guid.TryParse(id, out idGuid)) { - entity = Services.EntityService.GetByKey(idGuid, UmbracoObjectType); + return new Tuple(idGuid, null); } - else if (int.TryParse(id, out idInt)) + if (int.TryParse(id, out idInt)) { - entity = Services.EntityService.Get(idInt, UmbracoObjectType); + return new Tuple(null, idInt); } - else if (Udi.TryParse(id, out idUdi)) + if (Udi.TryParse(id, out idUdi)) { var guidUdi = idUdi as GuidUdi; - entity = guidUdi != null ? Services.EntityService.GetByKey(guidUdi.Guid, UmbracoObjectType) : null; - } - else - { - return null; - } + if (guidUdi != null) + return new Tuple(guidUdi.Guid, null); + } - return entity; - } + return null; + } + + /// + /// Get an entity via an id that can be either an integer, Guid or UDI + /// + /// + /// + /// + /// This object has it's own contextual cache for these lookups + /// + internal IUmbracoEntity GetEntityFromId(string id) + { + return _entityCache.GetOrAdd(id, s => + { + IUmbracoEntity entity; + + Guid idGuid; + int idInt; + Udi idUdi; + + if (Guid.TryParse(s, out idGuid)) + { + entity = Services.EntityService.GetByKey(idGuid, UmbracoObjectType); + } + else if (int.TryParse(s, out idInt)) + { + entity = Services.EntityService.Get(idInt, UmbracoObjectType); + } + else if (Udi.TryParse(s, out idUdi)) + { + var guidUdi = idUdi as GuidUdi; + entity = guidUdi != null ? Services.EntityService.GetByKey(guidUdi.Guid, UmbracoObjectType) : null; + } + else + { + return null; + } + + return entity; + }); + + + } + + private readonly ConcurrentDictionary _entityCache = new ConcurrentDictionary(); } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 7f3a6dfddf..33ae50dbbf 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -95,12 +95,19 @@ namespace Umbraco.Web.Trees menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + menu.Items.Add(new MenuItem("rename", Services.TextService.Localize(String.Format("actions/{0}", "rename"))) + { + Icon = "icon icon-edit" + }); + if (container.HasChildren() == false) { //can delete doc type menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)), true); } - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); + + } else { diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index b19d79c817..70da27c1bf 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -105,12 +105,18 @@ namespace Umbraco.Web.Trees menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + menu.Items.Add(new MenuItem("rename", Services.TextService.Localize(String.Format("actions/{0}", "rename"))) + { + Icon = "icon icon-edit" + }); + if (container.HasChildren() == false) { //can delete data type menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); } - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), hasSeparator: true); + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), hasSeparator: true); + } else { diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index 679d4a2d6a..99117b3b04 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -36,20 +36,7 @@ namespace Umbraco.Web.Trees public class MediaTreeController : ContentTreeControllerBase, ISearchableTree { private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); - - protected override TreeNode CreateRootNode(FormDataCollection queryStrings) - { - var node = base.CreateRootNode(queryStrings); - - // if the user's start node is not default, then ensure the root doesn't have a menu - if (UserStartNodes.Contains(Constants.System.Root) == false) - { - node.MenuUrl = ""; - } - node.Name = ui.Text("sections", Constants.Trees.Media); - return node; - } - + protected override int RecycleBinId { get { return Constants.System.RecycleBinMedia; } @@ -107,9 +94,12 @@ namespace Umbraco.Web.Trees if (id == Constants.System.Root.ToInvariantString()) { - //if the user's start node is not the root then ensure the root menu is empty/doesn't exist + // if the user's start node is not the root then the only menu item to display is refresh if (UserStartNodes.Contains(Constants.System.Root) == false) { + menu.Items.Add( + Services.TextService.Localize(string.Concat("actions/", ActionRefresh.Instance.Alias)), + true); return menu; } @@ -130,6 +120,16 @@ namespace Umbraco.Web.Trees { throw new HttpResponseException(HttpStatusCode.NotFound); } + + //if the user has no path access for this node, all they can do is refresh + if (Security.CurrentUser.HasPathAccess(item, Services.EntityService, RecycleBinId) == false) + { + menu.Items.Add( + Services.TextService.Localize(string.Concat("actions/", ActionRefresh.Instance.Alias)), + true); + return menu; + } + //return a normal node menu: menu.Items.Add(ui.Text("actions", ActionNew.Instance.Alias)); menu.Items.Add(ui.Text("actions", ActionMove.Instance.Alias)); @@ -160,17 +160,7 @@ namespace Umbraco.Web.Trees protected override bool HasPathAccess(string id, FormDataCollection queryStrings) { var entity = GetEntityFromId(id); - if (entity == null) - { - return false; - } - - var media = Services.MediaService.GetById(entity.Id); - if (media == null) - { - return false; - } - return Security.CurrentUser.HasPathAccess(media, Services.EntityService); + return HasPathAccess(entity, queryStrings); } public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index e402b5d64b..c0fde0db92 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -87,13 +87,20 @@ namespace Umbraco.Web.Trees menu.DefaultMenuAlias = ActionNew.Instance.Alias; menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - + + menu.Items.Add(new MenuItem("rename", Services.TextService.Localize(String.Format("actions/{0}", "rename"))) + { + Icon = "icon icon-edit" + }); + if (container.HasChildren() == false) { //can delete doc type menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); } menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), hasSeparator: true); + + } else { diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index c5c3c3dc70..33ce91cf6d 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -94,10 +94,7 @@ namespace Umbraco.Web.Trees if (template.IsMasterTemplate == false) { //add delete option if it doesn't have children - menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias), true) - //Since we haven't implemented anything for languages in angular, this needs to be converted to - //use the legacy format - .ConvertLegacyMenuItem(entity, "templates", queryStrings.GetValue("application")); + menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias), true); } //add refresh diff --git a/src/Umbraco.Web/UI/LegacyDialogHandler.cs b/src/Umbraco.Web/UI/LegacyDialogHandler.cs index dfa7fbc153..c51a8bb08b 100644 --- a/src/Umbraco.Web/UI/LegacyDialogHandler.cs +++ b/src/Umbraco.Web/UI/LegacyDialogHandler.cs @@ -125,6 +125,10 @@ namespace Umbraco.Web.UI internal static bool UserHasCreateAccess(HttpContextBase httpContext, User umbracoUser, string nodeType) { var task = GetTaskForOperation(httpContext, umbracoUser, Operation.Create, nodeType); + if (task == null) + throw new InvalidOperationException( + string.Format("Could not task for operation {0} for node type {1}", Operation.Create, nodeType)); + var dialogTask = task as LegacyDialogTask; if (dialogTask != null) { @@ -149,6 +153,10 @@ namespace Umbraco.Web.UI internal static bool UserHasDeleteAccess(HttpContextBase httpContext, User umbracoUser, string nodeType) { var task = GetTaskForOperation(httpContext, umbracoUser, Operation.Delete, nodeType); + if (task == null) + throw new InvalidOperationException( + string.Format("Could not task for operation {0} for node type {1}", Operation.Delete, nodeType)); + var dialogTask = task as LegacyDialogTask; if (dialogTask != null) { @@ -160,7 +168,10 @@ namespace Umbraco.Web.UI public static void Delete(HttpContextBase httpContext, User umbracoUser, string nodeType, int nodeId, string text) { var typeInstance = GetTaskForOperation(httpContext, umbracoUser, Operation.Delete, nodeType); - + if (typeInstance == null) + throw new InvalidOperationException( + string.Format("Could not task for operation {0} for node type {1}", Operation.Delete, nodeType)); + typeInstance.ParentID = nodeId; typeInstance.Alias = text; @@ -170,7 +181,10 @@ namespace Umbraco.Web.UI public static string Create(HttpContextBase httpContext, User umbracoUser, string nodeType, int nodeId, string text, int typeId = 0) { var typeInstance = GetTaskForOperation(httpContext, umbracoUser, Operation.Create, nodeType); - + if (typeInstance == null) + throw new InvalidOperationException( + string.Format("Could not task for operation {0} for node type {1}", Operation.Create, nodeType)); + typeInstance.TypeID = typeId; typeInstance.ParentID = nodeId; typeInstance.Alias = text; @@ -187,6 +201,9 @@ namespace Umbraco.Web.UI internal static string Create(HttpContextBase httpContext, User umbracoUser, string nodeType, int nodeId, string text, IDictionary additionalValues, int typeId = 0) { var typeInstance = GetTaskForOperation(httpContext, umbracoUser, Operation.Create, nodeType); + if (typeInstance == null) + throw new InvalidOperationException( + string.Format("Could not task for operation {0} for node type {1}", Operation.Create, nodeType)); typeInstance.TypeID = typeId; typeInstance.ParentID = nodeId; diff --git a/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs b/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs index c09fd0fa0c..c78f66ab97 100644 --- a/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs +++ b/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs @@ -7,7 +7,9 @@ using Umbraco.Web.Security; using umbraco; using umbraco.BusinessLogic; using umbraco.businesslogic.Exceptions; +using umbraco.interfaces; using Umbraco.Core; +using Umbraco.Core.Models; using Umbraco.Core.Security; namespace Umbraco.Web.UI.Pages @@ -32,6 +34,40 @@ namespace Umbraco.Web.UI.Pages } } + /// + /// Performs an authorization check for the user against the requested entity/path and permission set, this is only relevant to content and media + /// + /// + /// + /// + protected void CheckPathAndPermissions(int entityId, UmbracoObjectTypes objectType, IAction actionToCheck) + { + if (objectType == UmbracoObjectTypes.Document || objectType == UmbracoObjectTypes.Media) + { + //check path access + + var entity = entityId == Constants.System.Root + ? UmbracoEntity.Root + : Services.EntityService.Get( + entityId, + objectType); + var hasAccess = Security.CurrentUser.HasPathAccess( + entity, + Services.EntityService, + objectType == UmbracoObjectTypes.Document ? Constants.System.RecycleBinContent : Constants.System.RecycleBinMedia); + if (hasAccess == false) + throw new UserAuthorizationException(string.Format("The current user doesn't have access to the path '{0}'", entity.Path)); + + //only documents have action permissions + if (objectType == UmbracoObjectTypes.Document) + { + var allowedActions = ActionsResolver.Current.FromActionSymbols(Security.CurrentUser.GetPermissions(entity.Path, Services.UserService)).ToArray(); + if (allowedActions.Contains(actionToCheck) == false) + throw new UserAuthorizationException(string.Format("The current user doesn't have permission to {0} on the path '{1}'", actionToCheck.Alias, entity.Path)); + } + } + } + private bool _hasValidated = false; /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c4a7b5e201..c9a3604020 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -491,6 +491,7 @@ + @@ -781,6 +782,7 @@ + @@ -1137,9 +1139,6 @@ - - ASPXCodeBehind - ASPXCodeBehind @@ -1227,6 +1226,8 @@ + + @@ -2004,6 +2005,5 @@ - \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 28960eb07d..d94476b31c 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -17,7 +17,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Web.Mvc; -using Umbraco.Core.Cache; +using Umbraco.Core.Cache; namespace Umbraco.Web { @@ -517,10 +517,10 @@ namespace Umbraco.Web public string UrlAbsolute(int contentId) { return UrlProvider.GetUrl(contentId, true); - } - + } + #endregion - + #region Members public IPublishedContent TypedMember(Udi id) @@ -533,7 +533,7 @@ namespace Umbraco.Web public IPublishedContent TypedMember(Guid id) { return MembershipHelper.GetByProviderKey(id); - } + } public IPublishedContent TypedMember(object id) { @@ -620,7 +620,7 @@ namespace Umbraco.Web { return ContentQuery.TypedContent(id); } - + /// /// Gets a content item from the cache /// @@ -968,10 +968,10 @@ namespace Umbraco.Web } guidIds = list; return true; - } - + } + #endregion - + #region Media public IPublishedContent TypedMedia(Udi id) @@ -982,15 +982,15 @@ namespace Umbraco.Web } 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 - + { + //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 /// @@ -1018,8 +1018,8 @@ namespace Umbraco.Web if (ConvertIdObjectToUdi(id, out udiId)) return TypedMedia(udiId); return null; - } - + } + public IPublishedContent TypedMedia(int id) { return ContentQuery.TypedMedia(id); @@ -1439,18 +1439,58 @@ namespace Umbraco.Web /// public IHtmlString Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent) { - return _stringUtilities.Truncate(html, length, addElipsis, treatTagsAsContent); - } + return _stringUtilities.Truncate(html, length, addElipsis, treatTagsAsContent); + } + #region Truncate by Words + /// + /// Truncates a string to a given amount of words, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// + public IHtmlString TruncateByWords(string html, int words) + { + int length = _stringUtilities.WordsToLength(html, words); - #endregion + return Truncate(html, length, true, false); + } - #region If + /// + /// Truncates a string to a given amount of words, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// + public IHtmlString TruncateByWords(string html, int words, bool addElipsis) + { + int length = _stringUtilities.WordsToLength(html, words); - /// - /// If the test is true, the string valueIfTrue will be returned, otherwise the valueIfFalse will be returned. - /// - public HtmlString If(bool test, string valueIfTrue, string valueIfFalse) + return Truncate(html, length, addElipsis, false); + } + + /// + /// Truncates a string to a given amount of words, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// + public IHtmlString TruncateByWords(IHtmlString html, int words) + { + int length = _stringUtilities.WordsToLength(html.ToHtmlString(), words); + + return Truncate(html, length, true, false); + } + + /// + /// Truncates a string to a given amount of words, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// + public IHtmlString TruncateByWords(IHtmlString html, int words, bool addElipsis) + { + int length = _stringUtilities.WordsToLength(html.ToHtmlString(), words); + + return Truncate(html, length, addElipsis, false); + } + #endregion + #endregion + + #region If + + /// + /// If the test is true, the string valueIfTrue will be returned, otherwise the valueIfFalse will be returned. + /// + public HtmlString If(bool test, string valueIfTrue, string valueIfFalse) { return test ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); } diff --git a/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs b/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs new file mode 100644 index 0000000000..1d35d19134 --- /dev/null +++ b/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs @@ -0,0 +1,17 @@ +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; + +namespace Umbraco.Web.WebApi +{ + /// + /// Ensures controllers have detailed error messages even when debug mode is off + /// + public class EnableDetailedErrorsAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(HttpActionContext actionContext) + { + actionContext.ControllerContext.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs index 25f85def0f..dfe8ee5ce5 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs @@ -23,6 +23,8 @@ namespace Umbraco.Web.WebApi [DisableBrowserCache] [UmbracoWebApiRequireHttps] [CheckIfUserTicketDataIsStale] + [UnhandedExceptionLoggerConfiguration] + [EnableDetailedErrors] public abstract class UmbracoAuthorizedApiController : UmbracoApiController { @@ -48,7 +50,7 @@ namespace Umbraco.Web.WebApi protected BackOfficeUserManager UserManager { get { return _userManager ?? (_userManager = TryGetOwinContext().Result.GetBackOfficeUserManager()); } - } + } private bool _userisValidated = false; diff --git a/src/Umbraco.Web/WebApi/UnhandedExceptionLoggerConfigurationAttribute.cs b/src/Umbraco.Web/WebApi/UnhandedExceptionLoggerConfigurationAttribute.cs new file mode 100644 index 0000000000..51c43b0821 --- /dev/null +++ b/src/Umbraco.Web/WebApi/UnhandedExceptionLoggerConfigurationAttribute.cs @@ -0,0 +1,29 @@ +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http.Controllers; +using System.Web.Http.ExceptionHandling; +using System.Web.Http.Filters; +using Umbraco.Core; +using Umbraco.Core.Logging; + +namespace Umbraco.Web.WebApi +{ + /// + /// Adds our unhandled exception logger to the controller's services + /// + /// + /// Important to note that the will only be called if the controller has an ExceptionFilter applied + /// to it, so to kill two birds with one stone, this class inherits from ExceptionFilterAttribute purely to force webapi to use the + /// IExceptionLogger (strange) + /// + public class UnhandedExceptionLoggerConfigurationAttribute : ExceptionFilterAttribute, IControllerConfiguration + { + public virtual void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Add(typeof(IExceptionLogger), new UnhandledExceptionLogger()); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/UnhandledExceptionLogger.cs b/src/Umbraco.Web/WebApi/UnhandledExceptionLogger.cs new file mode 100644 index 0000000000..7c6d9df294 --- /dev/null +++ b/src/Umbraco.Web/WebApi/UnhandledExceptionLogger.cs @@ -0,0 +1,36 @@ +using System.Web.Http.ExceptionHandling; +using Umbraco.Core; +using Umbraco.Core.Logging; + +namespace Umbraco.Web.WebApi +{ + /// + /// Used to log unhandled exceptions in webapi controllers + /// + public class UnhandledExceptionLogger : ExceptionLogger + { + private readonly ILogger _logger; + + public UnhandledExceptionLogger() + : this(ApplicationContext.Current.ProfilingLogger.Logger) + { + } + + public UnhandledExceptionLogger(ILogger logger) + { + _logger = logger; + } + + public override void Log(ExceptionLoggerContext context) + { + if (context != null && context.ExceptionContext != null + && context.ExceptionContext.ActionContext != null && context.ExceptionContext.ActionContext.ControllerContext != null + && context.ExceptionContext.ActionContext.ControllerContext.Controller != null + && context.Exception != null) + { + _logger.Error(context.ExceptionContext.ActionContext.ControllerContext.Controller.GetType(), "Unhandled controller exception occurred", context.Exception); + } + } + + } +} \ 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 54bb1ac2f1..a7d40fa088 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Text; diff --git a/src/Umbraco.Web/umbraco.presentation/library.cs b/src/Umbraco.Web/umbraco.presentation/library.cs index 4ab89c5c7d..5118de709e 100644 --- a/src/Umbraco.Web/umbraco.presentation/library.cs +++ b/src/Umbraco.Web/umbraco.presentation/library.cs @@ -1393,17 +1393,17 @@ namespace umbraco /// An XpathNodeIterator containing the current page as Xml. public static XPathNodeIterator GetXmlNodeCurrent() { - var pageId = ""; + var pageId = ""; try { var nav = Umbraco.Web.UmbracoContext.Current.ContentCache.GetXPathNavigator(); - var pageIdItem = HttpContext.Current.Items["pageID"]; - + var pageIdItem = HttpContext.Current.Items["pageID"]; + if (pageIdItem == null) { throw new NullReferenceException("pageID not found in the current HTTP context"); - } + } pageId = pageIdItem.ToString(); nav.MoveToId(pageId); @@ -1613,7 +1613,8 @@ namespace umbraco public static void SendMail(string fromMail, string toMail, string subject, string body, bool isHtml) { try - { + { + var mailSender = new EmailSender(); using (var mail = new MailMessage()) { mail.From = new MailAddress(fromMail.Trim()); @@ -1622,8 +1623,7 @@ namespace umbraco mail.Subject = subject; mail.IsBodyHtml = isHtml; mail.Body = body; - using (var smtpClient = new SmtpClient()) - smtpClient.Send(mail); + mailSender.Send(mail); } } catch (Exception ee) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/AssignDomain2.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/AssignDomain2.aspx.cs index 4f71e20d08..de1e563776 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/AssignDomain2.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/AssignDomain2.aspx.cs @@ -15,6 +15,14 @@ namespace umbraco.dialogs { public partial class AssignDomain2 : UmbracoEnsuredPage { + protected override void OnInit(EventArgs e) + { + base.OnInit(e); + + var nodeId = GetNodeId(); + CheckPathAndPermissions(nodeId, UmbracoObjectTypes.Document, ActionAssignDomain.Instance); + } + protected override void OnLoad(EventArgs e) { base.OnLoad(e); @@ -29,16 +37,7 @@ namespace umbraco.dialogs pane_domains.Visible = false; p_buttons.Visible = false; return; - } - - if (UmbracoUser.GetPermissions(node.Path).Contains(ActionAssignDomain.Instance.Letter) == false) - { - feedback.Text = ui.Text("assignDomain", "permissionDenied"); - pane_language.Visible = false; - pane_domains.Visible = false; - p_buttons.Visible = false; - return; - } + } pane_language.Title = ui.Text("assignDomain", "setLanguage"); pane_domains.Title = ui.Text("assignDomain", "setDomains"); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs deleted file mode 100644 index f8fc1d3ea3..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs +++ /dev/null @@ -1,442 +0,0 @@ -using System; -using System.Collections; -using System.Globalization; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Xml; -using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using umbraco.BasePages; -using System.Linq; -using umbraco.interfaces; -using Umbraco.Web; -using Umbraco.Core; - -namespace umbraco.dialogs -{ - /// - /// Summary description for moveOrCopy. - /// - public partial class moveOrCopy : UmbracoEnsuredPage - { - - protected override void OnInit(EventArgs e) - { - CurrentApp = Request["app"]; - - base.OnInit(e); - } - - protected void Page_Load(object sender, EventArgs e) - { - JTree.DataBind(); - - // Put user code to initialize the page here - if (IsPostBack == false) - { - pp_relate.Text = ui.Text("moveOrCopy", "relateToOriginal"); - - //Document Type copy Hack... - - if (CurrentApp == Constants.Applications.Settings) - { - pane_form.Visible = false; - pane_form_notice.Visible = false; - pane_settings.Visible = true; - - ok.Text = ui.Text("general", "ok", UmbracoUser); - ok.Attributes.Add("style", "width: 60px"); - - var documentType = Services.ContentTypeService.GetContentType(int.Parse(Request.GetItemAsString("id"))); - - //Load master types... - masterType.Attributes.Add("style", "width: 350px;"); - masterType.Items.Add(new ListItem(ui.Text("none") + "...", "0")); - - foreach (var docT in Services.ContentTypeService.GetAllContentTypes().OrderBy(x => x.Name)) - { - masterType.Items.Add(new ListItem(docT.Name, docT.Id.ToString(CultureInfo.InvariantCulture))); - } - - masterType.SelectedValue = (documentType.ParentId > 0 ? documentType.ParentId : 0).ToString(CultureInfo.InvariantCulture); - - rename.Text = documentType.Name + " (copy)"; - pane_settings.Text = "Make a copy of the document type '" + documentType.Name + "' and save it under a new name"; - - } - else - { - pane_form.Visible = true; - pane_form_notice.Visible = true; - - pane_settings.Visible = false; - - // Caption and properies on BUTTON - ok.Text = ui.Text("general", "ok", UmbracoUser); - ok.Attributes.Add("style", "width: 60px"); - ok.Attributes.Add("disabled", "true"); - - IContentBase currContent; - if (CurrentApp == "content") - { - currContent = Services.ContentService.GetById(Request.GetItemAs("id")); - } - else - { - currContent = Services.MediaService.GetById(Request.GetItemAs("id")); - } - - // Preselect the parent of the seslected item. - if (currContent.ParentId > 0) - JTree.SelectedNodePath = currContent.Path.Substring(0, currContent.Path.LastIndexOf(',')); - - var validAction = true; - if (CurrentApp == Constants.Applications.Content && Umbraco.Core.Models.ContentExtensions.HasChildren(currContent, Services)) - { - validAction = ValidAction(currContent, Request.GetItemAsString("mode") == "cut" ? 'M' : 'O'); - } - - if (Request.GetItemAsString("mode") == "cut") - { - pane_form.Text = ui.Text("moveOrCopy", "moveTo", currContent.Name, UmbracoUser); - pp_relate.Visible = false; - } - else - { - pane_form.Text = ui.Text("moveOrCopy", "copyTo", currContent.Name, UmbracoUser); - pp_relate.Visible = true; - } - - if (validAction == false) - { - panel_buttons.Visible = false; - ScriptManager.RegisterStartupScript(this, GetType(), "notvalid", "notValid();", true); - } - } - } - - } - - private bool ValidAction(IContentBase cmsNode, char actionLetter) - { - var currentAction = BusinessLogic.Actions.Action.GetPermissionAssignable().First(a => a.Letter == actionLetter); - return CheckPermissions(cmsNode, currentAction); - } - - /// - /// Checks if the current user has permissions to execute this action against this node - /// - /// - /// - /// - /// - /// This used to do a recursive check for all descendent nodes but this is not required and is a massive CPU hog. - /// See: http://issues.umbraco.org/issue/U4-2632, https://groups.google.com/forum/?fromgroups=#!topic/umbraco-dev/L1D4LwVSP2Y - /// - private bool CheckPermissions(IContentBase node, IAction currentAction) - { - var userService = ApplicationContext.Current.Services.UserService; - var currUserPermissions = userService.GetPermissions(UmbracoContext.Current.Security.CurrentUser, node.Id).GetAllPermissions(); - return currUserPermissions != null && currUserPermissions.Contains(currentAction.Letter.ToString(CultureInfo.InvariantCulture)); - } - - private void HandleDocumentTypeCopy() - { - var contentTypeService = ApplicationContext.Current.Services.ContentTypeService; - var contentType = contentTypeService.GetContentType( - int.Parse(Request.GetItemAsString("id"))); - - //set the master - //http://issues.umbraco.org/issue/U4-2843 - //http://issues.umbraco.org/issue/U4-3552 - var parentId = int.Parse(masterType.SelectedValue); - - var alias = rename.Text.Trim().Replace("'", "''"); - var clone = contentTypeService.Copy(contentType, alias, rename.Text.Trim(), parentId); - - var returnUrl = string.Format("{0}/settings/editNodeTypeNew.aspx?id={1}", SystemDirectories.Umbraco, clone.Id); - - pane_settings.Visible = false; - panel_buttons.Visible = false; - - feedback.Text = "Document type copied"; - feedback.type = uicontrols.Feedback.feedbacktype.success; - - ClientTools.ChangeContentFrameUrl(returnUrl); - } - - public void HandleMoveOrCopy(object sender, EventArgs e) - { - if (CurrentApp == Constants.Applications.Settings) - HandleDocumentTypeCopy(); - else - HandleDocumentMoveOrCopy(); - } - - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference("../webservices/cmsnode.asmx")); - ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference("../webservices/legacyAjaxCalls.asmx")); - } - - private void HandleDocumentMoveOrCopy() - { - if (Request.GetItemAsString("copyTo") != "" && Request.GetItemAsString("id") != "") - { - // Check if the current node is allowed at new position - var nodeAllowed = false; - - IContentBase currContent; - IContentBase parentContent = null; - IContentTypeBase parentContentType = null; - if (CurrentApp == "content") - { - currContent = Services.ContentService.GetById(Request.GetItemAs("id")); - if (Request.GetItemAs("copyTo") != -1) - { - parentContent = Services.ContentService.GetById(Request.GetItemAs("copyTo")); - if (parentContent != null) - { - parentContentType = Services.ContentTypeService.GetContentType(parentContent.ContentTypeId); - } - } - } - else - { - currContent = Services.MediaService.GetById(Request.GetItemAs("id")); - if (Request.GetItemAs("copyTo") != -1) - { - parentContent = Services.MediaService.GetById(Request.GetItemAs("copyTo")); - if (parentContent != null) - { - parentContentType = Services.ContentTypeService.GetMediaType(parentContent.ContentTypeId); - } - } - } - - // Check on contenttypes - if (parentContentType == null) - { - //check if this is allowed at root - IContentTypeBase currContentType; - if (CurrentApp == "content") - { - currContentType = Services.ContentTypeService.GetContentType(currContent.ContentTypeId); - } - else - { - currContentType = Services.ContentTypeService.GetMediaType(currContent.ContentTypeId); - } - nodeAllowed = currContentType.AllowedAsRoot; - if (!nodeAllowed) - { - feedback.Text = ui.Text("moveOrCopy", "notAllowedAtRoot", UmbracoUser); - feedback.type = uicontrols.Feedback.feedbacktype.error; - } - } - else - { - var allowedChildContentTypeIds = parentContentType.AllowedContentTypes.Select(x => x.Id).ToArray(); - if (allowedChildContentTypeIds.Any(x => x.Value == currContent.ContentTypeId)) - { - nodeAllowed = true; - } - - if (nodeAllowed == false) - { - feedback.Text = ui.Text("moveOrCopy", "notAllowedByContentType", UmbracoUser); - feedback.type = uicontrols.Feedback.feedbacktype.error; - } - else - { - // Check on paths - if ((string.Format(",{0},", parentContent.Path)).IndexOf(string.Format(",{0},", currContent.Id)) > -1) - { - nodeAllowed = false; - feedback.Text = ui.Text("moveOrCopy", "notAllowedByPath", UmbracoUser); - feedback.type = uicontrols.Feedback.feedbacktype.error; - } - } - } - - if (nodeAllowed) - { - pane_form.Visible = false; - pane_form_notice.Visible = false; - panel_buttons.Visible = false; - - var newNodeCaption = parentContent == null - ? ui.Text(CurrentApp) - : parentContent.Name; - - string[] nodes = { currContent.Name, newNodeCaption }; - - if (Request["mode"] == "cut") - { - if (CurrentApp == Constants.Applications.Content) - { - var doc = (IContent)currContent; - var copyToId = Request.GetItemAs("copyTo"); - Services.ContentService.Move(doc, copyToId, UmbracoUser.Id); - - } - else - { - var media = (IMedia)currContent; - var copyToId = Request.GetItemAs("copyTo"); - Services.MediaService.Move(media, copyToId, UmbracoUser.Id); - } - - feedback.Text = ui.Text("moveOrCopy", "moveDone", nodes, UmbracoUser) + "

" + ui.Text("closeThisWindow") + ""; - feedback.type = uicontrols.Feedback.feedbacktype.success; - - // refresh tree - ClientTools.MoveNode(currContent.Id.ToString(), currContent.Path); - } - else - { - //NOTE: We ONLY support Copy on content not media for some reason. - - var newContent = (IContent)currContent; - Services.ContentService.Copy(newContent, Request.GetItemAs("copyTo"), RelateDocuments.Checked, UmbracoUser.Id); - - feedback.Text = ui.Text("moveOrCopy", "copyDone", nodes, UmbracoUser) + "

" + ui.Text("closeThisWindow") + ""; - feedback.type = uicontrols.Feedback.feedbacktype.success; - - // refresh tree - ClientTools.CopyNode(currContent.Id.ToString(), newContent.Path); - } - } - } - } - - ///

- /// JsInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude1; - - /// - /// feedback control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.Feedback feedback; - - /// - /// pane_form control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.Pane pane_form; - - /// - /// JTree control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.controls.Tree.TreeControl JTree; - - /// - /// pp_relate control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.PropertyPanel pp_relate; - - /// - /// RelateDocuments control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.CheckBox RelateDocuments; - - /// - /// pane_form_notice control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder pane_form_notice; - - /// - /// pane_settings control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.Pane pane_settings; - - /// - /// PropertyPanel1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.PropertyPanel PropertyPanel1; - - /// - /// masterType control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.ListBox masterType; - - /// - /// rename control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox rename; - - /// - /// RequiredFieldValidator1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidator1; - - /// - /// panel_buttons control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Panel panel_buttons; - - /// - /// ok control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Button ok; - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs index a310764498..a7483901a9 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sendToTranslation.aspx.cs @@ -14,6 +14,7 @@ using umbraco.cms.businesslogic.web; using umbraco.cms.businesslogic; using umbraco.BusinessLogic; using umbraco.BasePages; +using Umbraco.Core; namespace umbraco.presentation.dialogs { @@ -71,7 +72,7 @@ namespace umbraco.presentation.dialogs // Translators foreach (var u in BusinessLogic.User.getAll()) - if (u.GetGroups().Select(x => x.ToLower()).Contains("translators") || UserHasTranslatePermission(u, _currentPage)) + if (u.GetGroups().Contains(Constants.Security.TranslatorGroupAlias, StringComparer.InvariantCultureIgnoreCase) || UserHasTranslatePermission(u, _currentPage)) translator.Items.Add(new ListItem(u.Name, u.Id.ToString())); if (translator.Items.Count == 0) { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs index f3dbb43519..f3bb4c8fdc 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs @@ -13,6 +13,8 @@ using umbraco.cms.businesslogic.media; using umbraco.cms.businesslogic.web; using System.Web.UI; using System.Collections.Generic; +using umbraco.businesslogic.Exceptions; +using Umbraco.Core.Models; namespace umbraco.cms.presentation { @@ -20,7 +22,13 @@ namespace umbraco.cms.presentation /// Summary description for sort. ///
public partial class sort : UmbracoEnsuredPage - { + { + /// + /// The Parent Id being sorted + /// + protected int? ParentIdAsInt { get; private set; } + protected string ParentIdAsString { get; private set; } + private readonly List _nodes = new List(); protected bool HideDateColumn @@ -33,6 +41,21 @@ namespace umbraco.cms.presentation { CurrentApp = helper.Request("app"); + ParentIdAsString = Request.GetItemAsString("ID"); + int parentId; + if (int.TryParse(ParentIdAsString, out parentId)) + { + ParentIdAsInt = parentId; + + if (CurrentApp == Constants.Applications.Content || CurrentApp == Constants.Applications.Media) + { + CheckPathAndPermissions( + ParentIdAsInt.Value, + CurrentApp == Constants.Applications.Content ? UmbracoObjectTypes.Document : UmbracoObjectTypes.Media, + ActionSort.Instance); + } + } + base.OnInit(e); } @@ -50,23 +73,22 @@ namespace umbraco.cms.presentation var app = Request.GetItemAsString("app"); var icon = "../images/umbraco/doc.gif"; - - int parentId; - if (int.TryParse(Request.GetItemAsString("ID"), out parentId)) + + if (ParentIdAsInt.HasValue) { if (app == Constants.Applications.Media) { icon = "../images/umbraco/mediaPhoto.gif"; var mediaService = ApplicationContext.Current.Services.MediaService; - if (parentId == -1) + if (ParentIdAsInt.Value == -1) { foreach (var child in mediaService.GetRootMedia().ToList().OrderBy(x => x.SortOrder)) _nodes.Add(CreateNode(child.Id.ToInvariantString(), child.SortOrder, child.Name, child.CreateDate, icon)); } else { - var children = mediaService.GetChildren(parentId); + var children = mediaService.GetChildren(ParentIdAsInt.Value); foreach (var child in children.OrderBy(x => x.SortOrder)) _nodes.Add(CreateNode(child.Id.ToInvariantString(), child.SortOrder, child.Name, child.CreateDate, icon)); } @@ -76,14 +98,14 @@ namespace umbraco.cms.presentation { var contentService = ApplicationContext.Current.Services.ContentService; - if (parentId == -1) + if (ParentIdAsInt.Value == -1) { foreach (var child in contentService.GetRootContent().ToList().OrderBy(x => x.SortOrder)) _nodes.Add(CreateNode(child.Id.ToInvariantString(), child.SortOrder, child.Name, child.CreateDate, icon)); } else { - var children = contentService.GetChildren(parentId); + var children = contentService.GetChildren(ParentIdAsInt.Value); foreach (var child in children) _nodes.Add(CreateNode(child.Id.ToInvariantString(), child.SortOrder, child.Name, child.CreateDate, icon)); } @@ -100,7 +122,7 @@ namespace umbraco.cms.presentation HideDateColumn = true; - var stylesheetName = Request.GetItemAsString("ID"); + var stylesheetName = ParentIdAsString; if (stylesheetName.IsNullOrWhiteSpace())throw new NullReferenceException("No Id passed in to editor"); var stylesheet = Services.FileService.GetStylesheetByName(stylesheetName.EnsureEndsWith(".css")); if (stylesheet == null) throw new InvalidOperationException("No stylesheet found by name " + stylesheetName); diff --git a/src/UmbracoExamine/UmbracoExamine.csproj b/src/UmbracoExamine/UmbracoExamine.csproj index b76bdbc029..8fdd5b2d04 100644 --- a/src/UmbracoExamine/UmbracoExamine.csproj +++ b/src/UmbracoExamine/UmbracoExamine.csproj @@ -188,7 +188,6 @@ -