diff --git a/.editorconfig b/.editorconfig index 80eabec233..29e21d01ed 100644 --- a/.editorconfig +++ b/.editorconfig @@ -27,4 +27,10 @@ dotnet_naming_symbols.private_fields.applicable_kinds = field dotnet_naming_symbols.private_fields.applicable_accessibilities = private dotnet_naming_style.prefix_underscore.capitalization = camel_case -dotnet_naming_style.prefix_underscore.required_prefix = _ \ No newline at end of file +dotnet_naming_style.prefix_underscore.required_prefix = _ + +# https://github.com/MicrosoftDocs/visualstudio-docs/blob/master/docs/ide/editorconfig-code-style-settings-reference.md +[*.cs] +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion \ No newline at end of file diff --git a/docs/BUILD.md b/.github/BUILD.md similarity index 100% rename from docs/BUILD.md rename to .github/BUILD.md diff --git a/docs/CLEAR.md b/.github/CLEAR.md similarity index 100% rename from docs/CLEAR.md rename to .github/CLEAR.md diff --git a/docs/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 100% rename from docs/CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md diff --git a/docs/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from docs/CONTRIBUTING.md rename to .github/CONTRIBUTING.md diff --git a/docs/CONTRIBUTING_DETAILED.md b/.github/CONTRIBUTING_DETAILED.md similarity index 96% rename from docs/CONTRIBUTING_DETAILED.md rename to .github/CONTRIBUTING_DETAILED.md index bffac4801b..020346dc5e 100644 --- a/docs/CONTRIBUTING_DETAILED.md +++ b/.github/CONTRIBUTING_DETAILED.md @@ -129,7 +129,7 @@ We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-fl The easiest way to get started is to run `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`. See [this page](BUILD.md) for more details. -Alternatively, you can open `src\umbraco.sln` in Visual Studio 2017 ([the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile. +Alternatively, you can open `src\umbraco.sln` in Visual Studio 2017 (version 15.3 or higher, [the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile. ![Gulp build in Visual Studio](img/gulpbuild.png) @@ -156,4 +156,4 @@ git rebase upstream/dev-v7 In this command we're syncing with the `dev-v7` branch, but you can of course choose another one if needed. -(More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated)) \ No newline at end of file +(More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated)) diff --git a/docs/CONTRIBUTION_GUIDELINES.md b/.github/CONTRIBUTION_GUIDELINES.md similarity index 100% rename from docs/CONTRIBUTION_GUIDELINES.md rename to .github/CONTRIBUTION_GUIDELINES.md diff --git a/.github/ISSUE_TEMPLATE/1_Bug.md b/.github/ISSUE_TEMPLATE/1_Bug.md new file mode 100644 index 0000000000..a1e33e3854 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1_Bug.md @@ -0,0 +1,66 @@ +--- +name: 🐛 Bug Report +about: File a bug report, if you've discovered a problem in Umbraco. +--- + +A brief description of the issue goes here. + + + + + +Reproduction +------------ + +If you're filing a bug, please describe how to reproduce it. Include as much +relevant information as possible, such as: + +### Bug summary + + + +### Specifics + + + +### Steps to reproduce + + + +### Expected result + + + +### Actual result + + diff --git a/.github/ISSUE_TEMPLATE/2_Feature_request.md b/.github/ISSUE_TEMPLATE/2_Feature_request.md new file mode 100644 index 0000000000..16ec2568dd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2_Feature_request.md @@ -0,0 +1,31 @@ +--- +name: 📮 Feature Request +about: Open a feature request, if you want to propose a new feature. +--- + +A brief description of your feature request goes here. + + + + +How can you help? +------------------------------- + + diff --git a/.github/ISSUE_TEMPLATE/3_Support_question.md b/.github/ISSUE_TEMPLATE/3_Support_question.md new file mode 100644 index 0000000000..829df982f9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3_Support_question.md @@ -0,0 +1,9 @@ +--- +name: ⁉️ Support Question +about: Having trouble with Umbraco? -> https://our.umbraco.com +--- + +This issue tracker is NOT meant for support questions. If you have a question, +please join us on the forum at https://our.umbraco.com. + +Thanks! diff --git a/.github/ISSUE_TEMPLATE/4_Documentation_issue.md b/.github/ISSUE_TEMPLATE/4_Documentation_issue.md new file mode 100644 index 0000000000..8893647aa8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/4_Documentation_issue.md @@ -0,0 +1,9 @@ +--- +name: 📖 Documentation Issue +about: See https://github.com/umbraco/UmbracoDocs/issues for documentation issues +--- + +The Umbraco documentation has its own dedicated repository. Please open your +documentation-related issue at https://github.com/umbraco/UmbracoDocs/issues + +Thanks! diff --git a/.github/ISSUE_TEMPLATE/5_Security_issue.md b/.github/ISSUE_TEMPLATE/5_Security_issue.md new file mode 100644 index 0000000000..84c5f5989c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/5_Security_issue.md @@ -0,0 +1,31 @@ +--- +name: 🔐 Security Issue +about: Discovered a Security Issue in Umbraco? +--- + +⚠️ PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, SEE BELOW. + +If you have found a security issue in Umbraco, please send the details to +security@umbraco.com and don't disclose it publicly until we can provide a fix for +it. If you wish, we'll credit you for finding verified issues, when we release +the patched version. + +❗ Please read more about how to report security issues on https://umbraco.com/security + +A note on "Self XSS" +-------------------- + +Umbraco is a CMS, that allows users to edit content on a website. As such, +all _authenticated users_ can: + + - Edit content, and (depending on the field types) insert HTML and CSS in that + content, with a variety of allowed attributes. + - Depending on the user level: Edit template files, and insert C#, HTML, CSS and + javascript in so on. + - Upload files to the site, which will become publicly available. + +We see these functionalities as _features_, and not as security issues. Please +report the mentioned items only if they can be performed by non-authorized +users, or other exploitable vulnerabilities. + +Thanks! diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..6275d161dc --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +### Prerequisites + +- [ ] I have [created an issue](https://github.com/umbraco/Umbraco-CMS/issues) for the proposed changes in this PR, the link is: +- [ ] I have added steps to test this contribution in the description below + +### Description + + + + + diff --git a/docs/README.md b/.github/README.md similarity index 91% rename from docs/README.md rename to .github/README.md index 5316ad0604..83de27c859 100644 --- a/docs/README.md +++ b/.github/README.md @@ -48,4 +48,5 @@ Umbraco is contribution focused and community driven. If you want to contribute Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](CONTRIBUTING_DETAILED.md#reporting-bugs). -To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org). +You can comment and report issues on the [github issue tracker](https://github.com/umbraco/Umbraco-CMS/issues). +Since [September 2018](https://umbraco.com/blog/a-second-take-on-umbraco-issue-tracker-hello-github-issues/) the old issue tracker is in read only mode, but can still be found at [http://issues.umbraco.org](http://issues.umbraco.org). diff --git a/docs/REVIEW_PROCESS.md b/.github/REVIEW_PROCESS.md similarity index 100% rename from docs/REVIEW_PROCESS.md rename to .github/REVIEW_PROCESS.md diff --git a/docs/V8_GETTING_STARTED.md b/.github/V8_GETTING_STARTED.md similarity index 100% rename from docs/V8_GETTING_STARTED.md rename to .github/V8_GETTING_STARTED.md diff --git a/docs/img/clonefork.png b/.github/img/clonefork.png similarity index 100% rename from docs/img/clonefork.png rename to .github/img/clonefork.png diff --git a/docs/img/createpullrequest.png b/.github/img/createpullrequest.png similarity index 100% rename from docs/img/createpullrequest.png rename to .github/img/createpullrequest.png diff --git a/docs/img/defaultbranch.png b/.github/img/defaultbranch.png similarity index 100% rename from docs/img/defaultbranch.png rename to .github/img/defaultbranch.png diff --git a/docs/img/forkrepository.png b/.github/img/forkrepository.png similarity index 100% rename from docs/img/forkrepository.png rename to .github/img/forkrepository.png diff --git a/docs/img/gulpbuild.png b/.github/img/gulpbuild.png similarity index 100% rename from docs/img/gulpbuild.png rename to .github/img/gulpbuild.png diff --git a/docs/img/vimeo.png b/.github/img/vimeo.png similarity index 100% rename from docs/img/vimeo.png rename to .github/img/vimeo.png diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index b142e08c5f..daa0018668 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -1,92 +1,56 @@ - - UmbracoCms.Core - 8.0.0 - Umbraco Cms Core Binaries - Umbraco HQ - Umbraco HQ - http://opensource.org/licenses/MIT - http://umbraco.com/ - http://umbraco.com/media/357769/100px_transparent.png - false - Contains the core assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms-package to setup Umbraco in Visual Studio as an ASP.NET project. - Contains the core assemblies needed to run Umbraco Cms - en-US - umbraco - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + UmbracoCms.Core + 8.0.0 + Umbraco Cms Core Binaries + Umbraco HQ + Umbraco HQ + http://opensource.org/licenses/MIT + http://umbraco.com/ + http://umbraco.com/media/357769/100px_transparent.png + false + Contains the core assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms package to setup Umbraco in Visual Studio as an ASP.NET project. + Contains the core assemblies needed to run Umbraco Cms + en-US + umbraco + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + - - - - - - - - - - - - + + + + diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec new file mode 100644 index 0000000000..e9bd8ca6ea --- /dev/null +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -0,0 +1,62 @@ + + + + UmbracoCms.Web + 8.0.0 + Umbraco Cms Core Binaries + Umbraco HQ + Umbraco HQ + http://opensource.org/licenses/MIT + http://umbraco.com/ + http://umbraco.com/media/357769/100px_transparent.png + false + Contains the web assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms package to setup Umbraco in Visual Studio as an ASP.NET project. + Contains the core assemblies needed to run Umbraco Cms + en-US + umbraco + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 1deec235f8..434fe812ef 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -1,58 +1,65 @@ - - UmbracoCms - 8.0.0 - Umbraco Cms - Umbraco HQ - Umbraco HQ - http://opensource.org/licenses/MIT - http://umbraco.com/ - http://umbraco.com/media/357769/100px_transparent.png - false - Installs Umbraco Cms in your Visual Studio ASP.NET project - Installs Umbraco Cms in your Visual Studio ASP.NET project - en-US - umbraco - - - - - - - - - - - - - - - - - - + + UmbracoCms + 8.0.0 + Umbraco Cms + Umbraco HQ + Umbraco HQ + http://opensource.org/licenses/MIT + http://umbraco.com/ + http://umbraco.com/media/357769/100px_transparent.png + false + Installs Umbraco Cms in your Visual Studio ASP.NET project + Installs Umbraco Cms in your Visual Studio ASP.NET project + en-US + umbraco + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - - - - - - - + + + + + - - + + + + + + + + + + + + + diff --git a/build/NuSpecs/build/UmbracoCms.targets b/build/NuSpecs/build/UmbracoCms.targets index fde5b4ea81..ac0e9e976d 100644 --- a/build/NuSpecs/build/UmbracoCms.targets +++ b/build/NuSpecs/build/UmbracoCms.targets @@ -47,9 +47,6 @@ umbraco - - umbraco_client - . diff --git a/build/NuSpecs/tools/Readme.txt b/build/NuSpecs/tools/Readme.txt index bcac64979d..53915436ac 100644 --- a/build/NuSpecs/tools/Readme.txt +++ b/build/NuSpecs/tools/Readme.txt @@ -1,26 +1,26 @@ - _ _ __ __ ____ _____ _____ ____ - | | | | \/ | _ \| __ \ /\ / ____/ __ \ + _ _ __ __ ____ _____ _____ ____ + | | | | \/ | _ \| __ \ /\ / ____/ __ \ | | | | \ / | |_) | |__) | / \ | | | | | | | | | | |\/| | _ <| _ / / /\ \| | | | | | | |__| | | | | |_) | | \ \ / ____ | |___| |__| | - \____/|_| |_|____/|_| \_/_/ \_\_____\____/ - + \____/|_| |_|____/|_| \_/_/ \_\_____\____/ + ---------------------------------------------------- Don't forget to build! -When upgrading your website using NuGet you should answer "No" to the questions to overwrite the Web.config -file (and config files in the config folder). +When upgrading your website using NuGet you should answer "No" to the questions to overwrite the Web.config +file (and config files in the config folder). -This NuGet package includes build targets that extend the creation of a deploy package, which is generated by +This NuGet package includes build targets that extend the creation of a deploy package, which is generated by Publishing from Visual Studio. The targets will only work once Publishing is configured, so if you don't use Publish this won't affect you. -The following items will now be automatically included when creating a deploy package or publishing to the file -system: umbraco, umbraco_client, config\splashes and global.asax. +The following items will now be automatically included when creating a deploy package or publishing to the file +system: umbraco, config\splashes and global.asax. Please read the release notes on our.umbraco.com: -http://our.umbraco.com/contribute/releases +https://our.umbraco.com/download/releases -- Umbraco \ No newline at end of file +- Umbraco diff --git a/build/NuSpecs/tools/ReadmeUpgrade.txt b/build/NuSpecs/tools/ReadmeUpgrade.txt index ff5c80b1c2..df364c64ed 100644 --- a/build/NuSpecs/tools/ReadmeUpgrade.txt +++ b/build/NuSpecs/tools/ReadmeUpgrade.txt @@ -1,39 +1,30 @@ - _ _ __ __ ____ _____ _____ ____ - | | | | \/ | _ \| __ \ /\ / ____/ __ \ + _ _ __ __ ____ _____ _____ ____ + | | | | \/ | _ \| __ \ /\ / ____/ __ \ | | | | \ / | |_) | |__) | / \ | | | | | | | | | | |\/| | _ <| _ / / /\ \| | | | | | | |__| | | | | |_) | | \ \ / ____ | |___| |__| | - \____/|_| |_|____/|_| \_/_/ \_\_____\____/ - + \____/|_| |_|____/|_| \_/_/ \_\_____\____/ + ---------------------------------------------------- -*** IMPORTANT NOTICE FOR UPGRADES FROM VERSIONS BELOW 7.7.0 *** - -Be sure to read the version specific upgrade information before proceeding: -https://our.umbraco.com/documentation/Getting-Started/Setup/Upgrading/version-specific#version-7-7-0 - -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! We've done our best to transform your configuration files but in case something is not quite right: remember we backed up your files in App_Data\NuGetBackup so you can find the original files before they were transformed. -We've overwritten all the files in the Umbraco and Umbraco_Client folder, these have been backed up in -App_Data\NuGetBackup. We didn't overwrite the UI.xml file nor did we remove any files or folders that you or -a package might have added. Only the existing files were overwritten. If you customized anything then make +We've overwritten all the files in the Umbraco folder, these have been backed up in +App_Data\NuGetBackup. We didn't overwrite the UI.xml file nor did we remove any files or folders that you or +a package might have added. Only the existing files were overwritten. If you customized anything then make sure to do a compare and merge with the NuGetBackup folder. -This NuGet package includes build targets that extend the creation of a deploy package, which is generated by +This NuGet package includes build targets that extend the creation of a deploy package, which is generated by Publishing from Visual Studio. The targets will only work once Publishing is configured, so if you don't use Publish this won't affect you. -The following items will now be automatically included when creating a deploy package or publishing to the file -system: umbraco, umbraco_client, config\splashes and global.asax. +The following items will now be automatically included when creating a deploy package or publishing to the file +system: umbraco, config\splashes and global.asax. Please read the release notes on our.umbraco.com: http://our.umbraco.com/contribute/releases -- Umbraco \ No newline at end of file +- Umbraco diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 027d1e49a6..fa3ab86ba2 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -150,10 +150,6 @@ - - - - diff --git a/build/NuSpecs/tools/applications.config.install.xdt b/build/NuSpecs/tools/applications.config.install.xdt index f4a0060150..5c05b2b4e9 100644 --- a/build/NuSpecs/tools/applications.config.install.xdt +++ b/build/NuSpecs/tools/applications.config.install.xdt @@ -1,11 +1,11 @@ - - - - - - - - - \ No newline at end of file + + + + + + + + + diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1 index 684739072c..0d4bb20b48 100644 --- a/build/NuSpecs/tools/install.ps1 +++ b/build/NuSpecs/tools/install.ps1 @@ -44,14 +44,6 @@ if ($project) { robocopy $umbracoFolder $umbracoBackupPath /e /LOG:$copyLogsPath\UmbracoBackup.log robocopy $umbracoFolderSource $umbracoFolder /is /it /e /xf UI.xml /LOG:$copyLogsPath\UmbracoCopy.log - $umbracoClientFolder = Join-Path $projectPath "Umbraco_Client" - New-Item -ItemType Directory -Force -Path $umbracoClientFolder - $umbracoClientFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco_Client" - $umbracoClientBackupPath = Join-Path $backupPath "Umbraco_Client" - New-Item -ItemType Directory -Force -Path $umbracoClientBackupPath - robocopy $umbracoClientFolder $umbracoClientBackupPath /e /LOG:$copyLogsPath\UmbracoClientBackup.log - robocopy $umbracoClientFolderSource $umbracoClientFolder /is /it /e /LOG:$copyLogsPath\UmbracoClientCopy.log - $copyWebconfig = $true $destinationWebConfig = Join-Path $projectPath "Web.config" @@ -85,15 +77,7 @@ if ($project) { $splashesDestination = Join-Path $projectPath "Config\splashes\" New-Item $splashesDestination -Type directory Copy-Item $splashesSource $splashesDestination -Force - - $sqlCe64Source = Join-Path $installPath "UmbracoFiles\bin\amd64\*" - $sqlCe64Destination = Join-Path $projectPath "bin\amd64\" - Copy-Item $sqlCe64Source $sqlCe64Destination -Force - - $sqlCex86Source = Join-Path $installPath "UmbracoFiles\bin\x86\*" - $sqlCex86Destination = Join-Path $projectPath "bin\x86\" - Copy-Item $sqlCex86source $sqlCex86Destination -Force - + $umbracoUIXMLSource = Join-Path $installPath "UmbracoFiles\Umbraco\Config\Create\UI.xml" $umbracoUIXMLDestination = Join-Path $projectPath "Umbraco\Config\Create\UI.xml" Copy-Item $umbracoUIXMLSource $umbracoUIXMLDestination -Force @@ -159,4 +143,4 @@ if ($project) { { $DTE.ItemOperations.OpenFile($toolsPath + '\ReadmeUpgrade.txt') } -} \ No newline at end of file +} diff --git a/build/build.ps1 b/build/build.ps1 index 964c29fb63..c7deb59f1e 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -69,6 +69,9 @@ $global:node_nodepath = $this.ClearEnvVar("NODEPATH") $global:node_npmcache = $this.ClearEnvVar("NPM_CONFIG_CACHE") $global:node_npmprefix = $this.ClearEnvVar("NPM_CONFIG_PREFIX") + + # https://github.com/gruntjs/grunt-contrib-connect/issues/235 + $this.SetEnvVar("NODE_NO_HTTP2", "1") }) $ubuild.DefineMethod("RestoreNode", @@ -78,6 +81,8 @@ $this.SetEnvVar("NODEPATH", $node_nodepath) $this.SetEnvVar("NPM_CONFIG_CACHE", $node_npmcache) $this.SetEnvVar("NPM_CONFIG_PREFIX", $node_npmprefix) + + $ignore = $this.ClearEnvVar("NODE_NO_HTTP2") }) $ubuild.DefineMethod("CompileBelle", @@ -124,7 +129,7 @@ Write-Output "### install gulp-cli" >> $log 2>&1 &npm install -g gulp-cli --quiet >> $log 2>&1 - if (-not $?) { throw "Failed to install gulp-cli" } # that one is expected to work + $error.Clear() # that one fails 'cos some files not being removed - ignore Write-Output "### gulp build for version $($this.Version.Release)" >> $log 2>&1 &gulp build --buildversion=$this.Version.Release >> $log 2>&1 @@ -399,9 +404,15 @@ -Symbols -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmscore.log" if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Core." } + &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Web.nuspec" ` + -Properties BuildTmp="$($this.BuildTemp)" ` + -Version "$($this.Version.Semver.ToString())" ` + -Symbols -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmsweb.log" + if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Web." } + &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.nuspec" ` -Properties BuildTmp="$($this.BuildTemp)" ` - -Version $this.Version.Semver.ToString() ` + -Version "$($this.Version.Semver.ToString())" ` -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cms.log" if (-not $?) { throw "Failed to pack NuGet UmbracoCms." } @@ -417,7 +428,7 @@ $ubuild.DefineMethod("VerifyNuGet", { $this.VerifyNuGetConsistency( - ("UmbracoCms", "UmbracoCms.Core"), + ("UmbracoCms", "UmbracoCms.Core", "UmbracoCms.Web"), ("Umbraco.Core", "Umbraco.Web", "Umbraco.Web.UI", "Umbraco.Examine")) if ($this.OnError()) { return } }) @@ -427,7 +438,7 @@ Write-Host "Prepare Azure Gallery" $this.CopyFile("$($this.SolutionRoot)\build\Azure\azuregalleryrelease.ps1", $this.BuildOutput) }) - + $ubuild.DefineMethod("Build", { $error.Clear() @@ -457,6 +468,7 @@ if ($this.OnError()) { return } $this.PrepareAzureGallery() if ($this.OnError()) { return } + Write-Host "Done" }) # ################################################################ @@ -472,5 +484,4 @@ $ubuild.Build() if ($ubuild.OnError()) { return } } - Write-Host "Done" if ($get) { return $ubuild } diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/docs/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index db1e5c88bd..0000000000 --- a/docs/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,11 +0,0 @@ -### Prerequisites - -- [ ] I have written a descriptive pull-request title -- [ ] I have linked this PR to an issue on the tracker at http://issues.umbraco.org - -### Description - - - - - diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index e6118de937..d7f81c1bb1 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -19,4 +19,4 @@ using System.Resources; // these are FYI and changed automatically [assembly: AssemblyFileVersion("8.0.0")] -[assembly: AssemblyInformationalVersion("8.0.0-alpha.49")] +[assembly: AssemblyInformationalVersion("8.0.0-alpha.52")] diff --git a/src/Umbraco.Core/Auditing/AuditEventsComponent.cs b/src/Umbraco.Core/Components/AuditEventsComponent.cs similarity index 78% rename from src/Umbraco.Core/Auditing/AuditEventsComponent.cs rename to src/Umbraco.Core/Components/AuditEventsComponent.cs index 57457f9241..134aa18414 100644 --- a/src/Umbraco.Core/Auditing/AuditEventsComponent.cs +++ b/src/Umbraco.Core/Components/AuditEventsComponent.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Text; using System.Threading; using System.Web; -using Umbraco.Core.Components; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -11,7 +10,7 @@ using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; -namespace Umbraco.Core.Auditing +namespace Umbraco.Core.Components { public sealed class AuditEventsComponent : UmbracoComponentBase, IUmbracoCoreComponent { @@ -53,18 +52,6 @@ namespace Umbraco.Core.Auditing _userService = userService; _entityService = entityService; - //BackOfficeUserManager.AccountLocked += ; - //BackOfficeUserManager.AccountUnlocked += ; - BackOfficeUserManager.ForgotPasswordRequested += OnForgotPasswordRequest; - BackOfficeUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange; - BackOfficeUserManager.LoginFailed += OnLoginFailed; - //BackOfficeUserManager.LoginRequiresVerification += ; - BackOfficeUserManager.LoginSuccess += OnLoginSuccess; - BackOfficeUserManager.LogoutSuccess += OnLogoutSuccess; - BackOfficeUserManager.PasswordChanged += OnPasswordChanged; - BackOfficeUserManager.PasswordReset += OnPasswordReset; - //BackOfficeUserManager.ResetAccessFailedCount += ; - UserService.SavedUserGroup += OnSavedUserGroupWithUsers; UserService.SavedUser += OnSavedUser; @@ -255,64 +242,6 @@ namespace Umbraco.Core.Auditing "umbraco/user/delete", "delete user"); } - private void OnLoginSuccess(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs) - { - var performingUser = GetPerformingUser(identityArgs.PerformingUser); - WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/login", "login success"); - } - } - - private void OnLogoutSuccess(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs) - { - var performingUser = GetPerformingUser(identityArgs.PerformingUser); - WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/logout", "logout success"); - } - } - - private void OnPasswordReset(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/reset", "password reset"); - } - } - - private void OnPasswordChanged(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/change", "password change"); - } - } - - private void OnLoginFailed(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, 0, identityArgs.IpAddress, "umbraco/user/sign-in/failed", "login failed", affectedDetails: ""); - } - } - - private void OnForgotPasswordChange(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change"); - } - } - - private void OnForgotPasswordRequest(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/request", "password forgot/request"); - } - } - private void WriteAudit(int performingId, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) { var performingUser = _userService.GetUserById(performingId); diff --git a/src/Umbraco.Core/Strategies/ManifestWatcherComponent.cs b/src/Umbraco.Core/Components/ManifestWatcherComponent.cs similarity index 91% rename from src/Umbraco.Core/Strategies/ManifestWatcherComponent.cs rename to src/Umbraco.Core/Components/ManifestWatcherComponent.cs index da2f3bba32..0a8d9ccd52 100644 --- a/src/Umbraco.Core/Strategies/ManifestWatcherComponent.cs +++ b/src/Umbraco.Core/Components/ManifestWatcherComponent.cs @@ -1,11 +1,9 @@ using System.IO; -using Umbraco.Core.Components; -using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; -namespace Umbraco.Core.Strategies +namespace Umbraco.Core.Components { [RuntimeLevel(MinLevel = RuntimeLevel.Run)] public class ManifestWatcherComponent : UmbracoComponentBase, IUmbracoCoreComponent diff --git a/src/Umbraco.Core/Strategies/RelateOnCopyComponent.cs b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs similarity index 93% rename from src/Umbraco.Core/Strategies/RelateOnCopyComponent.cs rename to src/Umbraco.Core/Components/RelateOnCopyComponent.cs index 754fc8dca4..4ebd309e9f 100644 --- a/src/Umbraco.Core/Strategies/RelateOnCopyComponent.cs +++ b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs @@ -1,11 +1,9 @@ -using System; -using Umbraco.Core.Components; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; -namespace Umbraco.Core.Strategies +namespace Umbraco.Core.Components { //TODO: This should just exist in the content service/repo! [RuntimeLevel(MinLevel = RuntimeLevel.Run)] diff --git a/src/Umbraco.Core/Strategies/RelateOnTrashComponent.cs b/src/Umbraco.Core/Components/RelateOnTrashComponent.cs similarity index 98% rename from src/Umbraco.Core/Strategies/RelateOnTrashComponent.cs rename to src/Umbraco.Core/Components/RelateOnTrashComponent.cs index 15f0a67a82..fffae85501 100644 --- a/src/Umbraco.Core/Strategies/RelateOnTrashComponent.cs +++ b/src/Umbraco.Core/Components/RelateOnTrashComponent.cs @@ -1,13 +1,11 @@ -using System; -using System.Linq; -using Umbraco.Core.Components; +using System.Linq; using Umbraco.Core.Composing; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; -namespace Umbraco.Core.Strategies +namespace Umbraco.Core.Components { [RuntimeLevel(MinLevel = RuntimeLevel.Run)] public sealed class RelateOnTrashComponent : UmbracoComponentBase, IUmbracoCoreComponent diff --git a/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs b/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs index 25399af5c7..f44afe394f 100644 --- a/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs +++ b/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs @@ -38,8 +38,6 @@ namespace Umbraco.Core.Composing.Composers container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); diff --git a/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs b/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs index 3455c4693e..ed8bece0e1 100644 --- a/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs +++ b/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs @@ -27,7 +27,6 @@ namespace Umbraco.Core.Composing.Composers // register the services container.RegisterSingleton(); container.RegisterSingleton(); - container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index 215840023a..cfadb37d6d 100644 --- a/src/Umbraco.Core/Composing/Current.cs +++ b/src/Umbraco.Core/Composing/Current.cs @@ -158,6 +158,9 @@ namespace Umbraco.Core.Composing public static IPublishedValueFallback PublishedValueFallback => _publishedValueFallback ?? Container.GetInstance() ?? new NoopPublishedValueFallback(); + public static IVariationContextAccessor VariationContextAccessor + => Container.GetInstance(); + #endregion } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index 332de45734..39861ac4e9 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -81,6 +81,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [ConfigurationProperty("loginBackgroundImage")] internal InnerTextConfigurationElement LoginBackgroundImage => GetOptionalTextElement("loginBackgroundImage", string.Empty); + [ConfigurationProperty("StripUdiAttributes")] + internal InnerTextConfigurationElement StripUdiAttributes + { + get { return GetOptionalTextElement("StripUdiAttributes", true); } + } + string IContentSection.NotificationEmailAddress => Notifications.NotificationEmailAddress; @@ -136,6 +142,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings bool IContentSection.EnableInheritedMediaTypes => EnableInheritedMediaTypes; + bool IContentSection.StripUdiAttributes => StripUdiAttributes; + string IContentSection.LoginBackgroundImage => LoginBackgroundImage; } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs index 519c0e6af1..82cc5928cf 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs @@ -1,9 +1,24 @@ -using System.Linq; +using System; +using System.Linq; namespace Umbraco.Core.Configuration.UmbracoSettings { public static class ContentSectionExtensions { + /// + /// Gets a value indicating whether the file extension corresponds to an image. + /// + /// The file extension. + /// + /// A value indicating whether the file extension corresponds to an image. + public static bool IsImageFile(this IContentSection contentConfig, string extension) + { + if (contentConfig == null) throw new ArgumentNullException(nameof(contentConfig)); + if (extension == null) return false; + extension = extension.TrimStart('.'); + return contentConfig.ImageFileTypes.InvariantContains(extension); + } + /// /// Determines if file extension is allowed for upload based on (optional) white list and black list /// held in settings. @@ -15,5 +30,17 @@ namespace Umbraco.Core.Configuration.UmbracoSettings (contentSection.AllowedUploadFiles.Any() == false && contentSection.DisallowedUploadFiles.Any(x => x.InvariantEquals(extension)) == false); } + + /// + /// Gets the auto-fill configuration for a specified property alias. + /// + /// + /// The property type alias. + /// The auto-fill configuration for the specified property alias, or null. + public static IImagingAutoFillUploadField GetConfig(this IContentSection contentSection, string propertyTypeAlias) + { + var autoFillConfigs = contentSection.ImageAutoFillProperties; + return autoFillConfigs?.FirstOrDefault(x => x.Alias == propertyTypeAlias); + } } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index 7f6f57f4cf..ef9ffeb014 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings IEnumerable ImageTagAllowedAttributes { get; } IEnumerable ImageAutoFillProperties { get; } - + string ScriptFolderPath { get; } IEnumerable ScriptFileTypes { get; } @@ -66,5 +66,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings bool EnableInheritedMediaTypes { get; } string LoginBackgroundImage { get; } + bool StripUdiAttributes { get; } + } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs index 1ce937d31c..f0a986efe2 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs @@ -8,6 +8,8 @@ bool DisableAlternativeTemplates { get; } + bool ValidateAlternativeTemplates { get; } + bool DisableFindContentByIdPath { get; } bool DisableRedirectUrlTracking { get; } @@ -16,5 +18,4 @@ string UmbracoApplicationUrl { get; } } - } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs index 9f82d15f7d..b4bd821338 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs @@ -5,44 +5,27 @@ namespace Umbraco.Core.Configuration.UmbracoSettings internal class WebRoutingElement : ConfigurationElement, IWebRoutingSection { [ConfigurationProperty("trySkipIisCustomErrors", DefaultValue = "false")] - public bool TrySkipIisCustomErrors - { - get { return (bool) base["trySkipIisCustomErrors"]; } - } + public bool TrySkipIisCustomErrors => (bool) base["trySkipIisCustomErrors"]; [ConfigurationProperty("internalRedirectPreservesTemplate", DefaultValue = "false")] - public bool InternalRedirectPreservesTemplate - { - get { return (bool) base["internalRedirectPreservesTemplate"]; } - } + public bool InternalRedirectPreservesTemplate => (bool) base["internalRedirectPreservesTemplate"]; [ConfigurationProperty("disableAlternativeTemplates", DefaultValue = "false")] - public bool DisableAlternativeTemplates - { - get { return (bool) base["disableAlternativeTemplates"]; } - } + public bool DisableAlternativeTemplates => (bool) base["disableAlternativeTemplates"]; + + [ConfigurationProperty("validateAlternativeTemplates", DefaultValue = "false")] + public bool ValidateAlternativeTemplates => (bool) base["validateAlternativeTemplates"]; + [ConfigurationProperty("disableFindContentByIdPath", DefaultValue = "false")] - public bool DisableFindContentByIdPath - { - get { return (bool) base["disableFindContentByIdPath"]; } - } + public bool DisableFindContentByIdPath => (bool) base["disableFindContentByIdPath"]; [ConfigurationProperty("disableRedirectUrlTracking", DefaultValue = "false")] - public bool DisableRedirectUrlTracking - { - get { return (bool) base["disableRedirectUrlTracking"]; } - } + public bool DisableRedirectUrlTracking => (bool) base["disableRedirectUrlTracking"]; [ConfigurationProperty("urlProviderMode", DefaultValue = "AutoLegacy")] - public string UrlProviderMode - { - get { return (string) base["urlProviderMode"]; } - } + public string UrlProviderMode => (string) base["urlProviderMode"]; [ConfigurationProperty("umbracoApplicationUrl", DefaultValue = null)] - public string UmbracoApplicationUrl - { - get { return (string)base["umbracoApplicationUrl"]; } - } + public string UmbracoApplicationUrl => (string)base["umbracoApplicationUrl"]; } } diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 8fb650510b..46ad221837 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Configuration /// /// Gets the version comment of the executing code (eg "beta"). /// - public static string CurrentComment => "alpha.49"; + public static string CurrentComment => "alpha.52"; /// /// Gets the assembly version of Umbraco.Code.dll. diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index 9a1883a065..ac42274a71 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -13,9 +13,9 @@ public const string Content = "content"; /// - /// Application alias for the developer section. + /// Application alias for the packages section. /// - public const string Developer = "developer"; + public const string Packages = "packages"; /// /// Application alias for the media section. diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 7f6f6e875f..eb1a6e2925 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -23,9 +23,6 @@ namespace Umbraco.Core { public const string MemberUsernameRuleType = "MemberUsername"; public const string MemberRoleRuleType = "MemberRole"; - - [Obsolete("No longer supported, this is here for backwards compatibility only")] - public const string MemberIdRuleType = "MemberId"; } @@ -94,6 +91,11 @@ namespace Umbraco.Core /// Property alias for the Media's file extension. /// public const string Extension = "umbracoExtension"; + + /// + /// The default height/width of an image file if the size can't be determined from the metadata + /// + public const int DefaultSize = 200; } /// diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index 790c143bbf..4bf944e1e1 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -71,9 +71,6 @@ namespace Umbraco.Core [Obsolete("This no longer exists in the database")] internal const string Stylesheet = "9F68DA4F-A3A8-44C2-8226-DCBD125E4840"; - [Obsolete("This no longer exists in the database")] - internal const string StylesheetProperty = "5555da4f-a123-42b2-4488-dcdfb25e4111"; - // ReSharper restore MemberHidesStaticFromOuterClass } diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index eaceff275e..abb92298f4 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -58,6 +58,7 @@ /// public const string RecycleBinMediaPathPrefix = "-1,-21,"; + public const int DefaultLabelDataTypeId = -92; public const string UmbracoConnectionName = "umbracoDbDSN"; public const string UmbracoUpgradePlanName = "Umbraco.Core"; } diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs similarity index 92% rename from src/Umbraco.Core/Models/ContentExtensions.cs rename to src/Umbraco.Core/ContentExtensions.cs index d8eb900bb2..e36731a8cb 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -7,12 +7,13 @@ using System.Web; using System.Xml.Linq; using Umbraco.Core.Composing; using Umbraco.Core.IO; +using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; -namespace Umbraco.Core.Models +namespace Umbraco.Core { public static class ContentExtensions { @@ -241,31 +242,6 @@ namespace Umbraco.Core.Models #region SetValue for setting file contents - /// - /// Sets the posted file value of a property. - /// - public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value, string culture = null, string segment = null) - { - // ensure we get the filename without the path in IE in intranet mode - // http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie - var filename = value.FileName; - var pos = filename.LastIndexOf(@"\", StringComparison.InvariantCulture); - if (pos > 0) - filename = filename.Substring(pos + 1); - - // strip any directory info - pos = filename.LastIndexOf(IOHelper.DirSepChar); - if (pos > 0) - filename = filename.Substring(pos + 1); - - // get a safe & clean filename - filename = IOHelper.SafeFileName(filename); - if (string.IsNullOrWhiteSpace(filename)) return; - filename = filename.ToLower(); // fixme - er... why? - - MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, value.InputStream, culture, segment); - } - /// /// Sets the posted file value of a property. /// @@ -281,7 +257,31 @@ namespace Umbraco.Core.Models if (string.IsNullOrWhiteSpace(filename)) return; filename = filename.ToLower(); // fixme - er... why? - MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, filestream, culture, segment); + SetUploadFile(content, propertyTypeAlias, filename, filestream, culture, segment); + } + + private static void SetUploadFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) + { + var property = GetProperty(content, propertyTypeAlias); + var oldpath = property.GetValue(culture, segment) is string svalue ? MediaFileSystem.GetRelativePath(svalue) : null; + var filepath = MediaFileSystem.StoreFile(content, property.PropertyType, filename, filestream, oldpath); + property.SetValue(MediaFileSystem.GetUrl(filepath), culture, segment); + } + + // gets or creates a property for a content item. + private static Property GetProperty(IContentBase content, string propertyTypeAlias) + { + var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + if (property != null) return property; + + var propertyType = content.GetContentType().CompositionPropertyTypes + .FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + if (propertyType == null) + throw new Exception("No property type exists with alias " + propertyTypeAlias + "."); + + property = new Property(propertyType); + content.Properties.Add(property); + return property; } /// diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs index 0d199d1d0d..c455fadad7 100644 --- a/src/Umbraco.Core/EnumerableExtensions.cs +++ b/src/Umbraco.Core/EnumerableExtensions.cs @@ -92,18 +92,7 @@ namespace Umbraco.Core } } - /// The flatten list. - /// The items. - /// The select child. - /// Item type - /// list of TItem - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Do not use, use SelectRecursive instead which has far less potential of re-iterating an iterator which may cause significantly more SQL queries")] - public static IEnumerable FlattenList(this IEnumerable e, Func> f) - { - return e.SelectMany(c => f(c).FlattenList(f)).Concat(e); - } - + /// /// Returns true if all items in the other collection exist in this collection /// diff --git a/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs b/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs index 0283ac372e..4c38c0b2ec 100644 --- a/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs +++ b/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs @@ -307,7 +307,7 @@ namespace Umbraco.Core.Events // fixme see notes above // delete event args does NOT superceedes 'unpublished' event - if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "UnPublished") + if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "Unpublished") return false; // found occurences, need to determine if this event args is superceded diff --git a/src/Umbraco.Core/HttpContextExtensions.cs b/src/Umbraco.Core/HttpContextExtensions.cs index e370b055a4..22eb4d1917 100644 --- a/src/Umbraco.Core/HttpContextExtensions.cs +++ b/src/Umbraco.Core/HttpContextExtensions.cs @@ -24,11 +24,19 @@ namespace Umbraco.Core { return "Unknown, httpContext is null"; } - if (httpContext.Request == null) + + HttpRequestBase request; + try + { + // is not null - throws + request = httpContext.Request; + } + catch { return "Unknown, httpContext.Request is null"; } - if (httpContext.Request.ServerVariables == null) + + if (request.ServerVariables == null) { return "Unknown, httpContext.Request.ServerVariables is null"; } @@ -37,16 +45,16 @@ namespace Umbraco.Core try { - var ipAddress = httpContext.Request.ServerVariables["HTTP_X_FORWARDED_FOR"]; + var ipAddress = request.ServerVariables["HTTP_X_FORWARDED_FOR"]; if (string.IsNullOrEmpty(ipAddress)) - return httpContext.Request.UserHostAddress; + return request.UserHostAddress; var addresses = ipAddress.Split(','); if (addresses.Length != 0) return addresses[0]; - return httpContext.Request.UserHostAddress; + return request.UserHostAddress; } catch (System.Exception ex) { diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index 794f7f5c20..1b0c8079f0 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -9,7 +8,6 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Media; -using Umbraco.Core.Media.Exif; using Umbraco.Core.Models; namespace Umbraco.Core.IO @@ -27,8 +25,6 @@ namespace Umbraco.Core.IO Logger = Current.Container.GetInstance(); MediaPathScheme = Current.Container.GetInstance(); MediaPathScheme.Initialize(this); - - UploadAutoFillProperties = new UploadAutoFillProperties(this, Logger, ContentConfig); } private IMediaPathScheme MediaPathScheme { get; } @@ -36,9 +32,7 @@ namespace Umbraco.Core.IO private IContentSection ContentConfig { get; } private ILogger Logger { get; } - - internal UploadAutoFillProperties UploadAutoFillProperties { get; } - + /// /// Deletes all files passed in. /// @@ -205,113 +199,9 @@ namespace Umbraco.Core.IO return filepath; } - // gets or creates a property for a content item. - private static Property GetProperty(IContentBase content, string propertyTypeAlias) - { - var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); - if (property != null) return property; - - var propertyType = content.GetContentType().CompositionPropertyTypes - .FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); - if (propertyType == null) - throw new Exception("No property type exists with alias " + propertyTypeAlias + "."); - - property = new Property(propertyType); - content.Properties.Add(property); - return property; - } - - // fixme - what's below belongs to the upload property editor, not the media filesystem! - - public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) - { - var property = GetProperty(content, propertyTypeAlias); - var oldpath = property.GetValue(culture, segment) is string svalue ? GetRelativePath(svalue) : null; - var filepath = StoreFile(content, property.PropertyType, filename, filestream, oldpath); - property.SetValue(GetUrl(filepath), culture, segment); - SetUploadFile(content, property, filepath, filestream, culture, segment); - } - - public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filepath, string culture = null, string segment = null) - { - var property = GetProperty(content, propertyTypeAlias); - // fixme delete? - var oldpath = property.GetValue(culture, segment) is string svalue ? GetRelativePath(svalue) : null; - if (string.IsNullOrWhiteSpace(oldpath) == false && oldpath != filepath) - DeleteFile(oldpath); - property.SetValue(GetUrl(filepath), culture, segment); - using (var filestream = OpenFile(filepath)) - { - SetUploadFile(content, property, filepath, filestream, culture, segment); - } - } - - // sets a file for the FileUpload property editor - // ie generates thumbnails and populates autofill properties - private void SetUploadFile(IContentBase content, Property property, string filepath, Stream filestream, string culture = null, string segment = null) - { - // will use filepath for extension, and filestream for length - UploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream, culture, segment); - } - - #endregion - - #region Image - - /// - /// Gets a value indicating whether the file extension corresponds to an image. - /// - /// The file extension. - /// A value indicating whether the file extension corresponds to an image. - public bool IsImageFile(string extension) - { - if (extension == null) return false; - extension = extension.TrimStart('.'); - return ContentConfig.ImageFileTypes.InvariantContains(extension); - } - - /// - /// Gets the dimensions of an image. - /// - /// A stream containing the image bytes. - /// The dimension of the image. - /// First try with EXIF as it is faster and does not load the entire image - /// in memory. Fallback to GDI which means loading the image in memory and thus - /// use potentially large amounts of memory. - public Size GetDimensions(Stream stream) - { - //Try to load with exif - try - { - var jpgInfo = ImageFile.FromStream(stream); - - if (jpgInfo.Format != ImageFileFormat.Unknown - && jpgInfo.Properties.ContainsKey(ExifTag.PixelYDimension) - && jpgInfo.Properties.ContainsKey(ExifTag.PixelXDimension)) - { - var height = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelYDimension].Value); - var width = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelXDimension].Value); - if (height > 0 && width > 0) - { - return new Size(width, height); - } - } - } - catch (Exception) - { - //We will just swallow, just means we can't read exif data, we don't want to log an error either - } - - //we have no choice but to try to read in via GDI - using (var image = Image.FromStream(stream)) - { - - var fileWidth = image.Width; - var fileHeight = image.Height; - return new Size(fileWidth, fileHeight); - } - } + #endregion + } } diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index f18a0e68f6..c8eedb1614 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -40,6 +40,7 @@ namespace Umbraco.Core.IO public static string Umbraco => IOHelper.ReturnPath("umbracoPath", "~/umbraco"); + [Obsolete("This will be removed, there is no more umbraco_client folder")] public static string UmbracoClient => IOHelper.ReturnPath("umbracoClientPath", "~/umbraco_client"); public static string UserControls => IOHelper.ReturnPath("umbracoUsercontrolsPath", "~/usercontrols"); diff --git a/src/Umbraco.Core/Logging/DisposableTimer.cs b/src/Umbraco.Core/Logging/DisposableTimer.cs index db530e5339..ed98e5cfab 100644 --- a/src/Umbraco.Core/Logging/DisposableTimer.cs +++ b/src/Umbraco.Core/Logging/DisposableTimer.cs @@ -30,17 +30,17 @@ namespace Umbraco.Core.Logging _endMessage = endMessage; _failMessage = failMessage; _thresholdMilliseconds = thresholdMilliseconds < 0 ? 0 : thresholdMilliseconds; - _timingId = Guid.NewGuid().ToString("N"); + _timingId = Guid.NewGuid().ToString("N").Substring(0, 7); // keep it short-ish if (thresholdMilliseconds == 0) { switch (_level) { case LogLevel.Debug: - logger.Debug(loggerType, "[Timing {TimingId}] {StartMessage}", _timingId, startMessage); + logger.Debug(loggerType, "{StartMessage} [Timing {TimingId}]", startMessage, _timingId); break; case LogLevel.Information: - logger.Info(loggerType, "[Timing {TimingId}] {StartMessage}", _timingId, startMessage); + logger.Info(loggerType, "{StartMessage} [Timing {TimingId}]", startMessage, _timingId); break; default: throw new ArgumentOutOfRangeException(nameof(level)); @@ -84,15 +84,15 @@ namespace Umbraco.Core.Logging { if (_failed) { - _logger.Error(_loggerType, _failException, "[Timing {TimingId}] {FailMessage} ({TimingDuration}ms)", _timingId, _failMessage, Stopwatch.ElapsedMilliseconds); + _logger.Error(_loggerType, _failException, "{FailMessage} ({Duration}ms) [Timing {TimingId}]", _failMessage, Stopwatch.ElapsedMilliseconds, _timingId); } else switch (_level) { case LogLevel.Debug: - _logger.Debug(_loggerType, "[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)", _timingId, _endMessage, Stopwatch.ElapsedMilliseconds); + _logger.Debug(_loggerType, "{EndMessage} ({Duration}ms) [Timing {TimingId}]", _endMessage, Stopwatch.ElapsedMilliseconds, _timingId); break; case LogLevel.Information: - _logger.Info(_loggerType, "[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)", _timingId, _endMessage, Stopwatch.ElapsedMilliseconds); + _logger.Info(_loggerType, "{EndMessage} ({Duration}ms) [Timing {TimingId}]", _endMessage, Stopwatch.ElapsedMilliseconds, _timingId); break; // filtered in the ctor //default: diff --git a/src/Umbraco.Core/Logging/LogHttpRequest.cs b/src/Umbraco.Core/Logging/LogHttpRequest.cs new file mode 100644 index 0000000000..34c1918b76 --- /dev/null +++ b/src/Umbraco.Core/Logging/LogHttpRequest.cs @@ -0,0 +1,32 @@ +using System; +using System.Web; + +namespace Umbraco.Core.Logging +{ + public static class LogHttpRequest + { + static readonly string RequestIdItemName = typeof(LogHttpRequest).Name + "+RequestId"; + + /// + /// Retrieve the id assigned to the currently-executing HTTP request, if any. + /// + /// The request id. + /// true if there is a request in progress; false otherwise. + public static bool TryGetCurrentHttpRequestId(out Guid requestId) + { + if (HttpContext.Current == null) + { + requestId = default(Guid); + return false; + } + + var requestIdItem = HttpContext.Current.Items[RequestIdItemName]; + if (requestIdItem == null) + HttpContext.Current.Items[RequestIdItemName] = requestId = Guid.NewGuid(); + else + requestId = (Guid)requestIdItem; + + return true; + } + } +} diff --git a/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpRequestIdEnricher.cs b/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpRequestIdEnricher.cs new file mode 100644 index 0000000000..2099698b6f --- /dev/null +++ b/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpRequestIdEnricher.cs @@ -0,0 +1,36 @@ +using System; +using Serilog.Core; +using Serilog.Events; + +namespace Umbraco.Core.Logging.Serilog.Enrichers +{ + /// + /// Enrich log events with a HttpRequestId GUID. + /// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpRequestIdEnricher.cs + /// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want + /// + internal class HttpRequestIdEnricher : ILogEventEnricher + { + /// + /// The property name added to enriched log events. + /// + public const string HttpRequestIdPropertyName = "HttpRequestId"; + + /// + /// Enrich the log event with an id assigned to the currently-executing HTTP request, if any. + /// + /// The log event to enrich. + /// Factory for creating new properties to add to the event. + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + if (logEvent == null) throw new ArgumentNullException("logEvent"); + + Guid requestId; + if (!LogHttpRequest.TryGetCurrentHttpRequestId(out requestId)) + return; + + var requestIdProperty = new LogEventProperty(HttpRequestIdPropertyName, new ScalarValue(requestId)); + logEvent.AddPropertyIfAbsent(requestIdProperty); + } + } +} diff --git a/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpRequestNumberEnricher.cs b/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpRequestNumberEnricher.cs new file mode 100644 index 0000000000..48415cccbc --- /dev/null +++ b/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpRequestNumberEnricher.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading; +using System.Web; +using Serilog.Core; +using Serilog.Events; + +namespace Umbraco.Core.Logging.Serilog.Enrichers +{ + /// + /// Enrich log events with a HttpRequestNumber unique within the current + /// logging session. + /// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpRequestNumberEnricher.cs + /// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want + /// + internal class HttpRequestNumberEnricher : ILogEventEnricher + { + /// + /// The property name added to enriched log events. + /// + public const string HttpRequestNumberPropertyName = "HttpRequestNumber"; + + static int _lastRequestNumber; + static readonly string RequestNumberItemName = typeof(HttpRequestNumberEnricher).Name + "+RequestNumber"; + + /// + /// Enrich the log event with the number assigned to the currently-executing HTTP request, if any. + /// + /// The log event to enrich. + /// Factory for creating new properties to add to the event. + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + if (logEvent == null) throw new ArgumentNullException("logEvent"); + + if (HttpContext.Current == null) + return; + + int requestNumber; + var requestNumberItem = HttpContext.Current.Items[RequestNumberItemName]; + if (requestNumberItem == null) + HttpContext.Current.Items[RequestNumberItemName] = requestNumber = Interlocked.Increment(ref _lastRequestNumber); + else + requestNumber = (int)requestNumberItem; + + var requestNumberProperty = new LogEventProperty(HttpRequestNumberPropertyName, new ScalarValue(requestNumber)); + logEvent.AddPropertyIfAbsent(requestNumberProperty); + } + } +} diff --git a/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpSessionIdEnricher.cs b/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpSessionIdEnricher.cs new file mode 100644 index 0000000000..d2fbfd4627 --- /dev/null +++ b/src/Umbraco.Core/Logging/Serilog/Enrichers/HttpSessionIdEnricher.cs @@ -0,0 +1,39 @@ +using Serilog.Core; +using Serilog.Events; +using System; +using System.Web; + +namespace Umbraco.Core.Logging.Serilog.Enrichers +{ + /// + /// Enrich log events with the HttpSessionId property. + /// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpSessionIdEnricher.cs + /// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want + /// + internal class HttpSessionIdEnricher : ILogEventEnricher + { + /// + /// The property name added to enriched log events. + /// + public const string HttpSessionIdPropertyName = "HttpSessionId"; + + /// + /// Enrich the log event with the current ASP.NET session id, if sessions are enabled. + /// The log event to enrich. + /// Factory for creating new properties to add to the event. + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + if (logEvent == null) throw new ArgumentNullException("logEvent"); + + if (HttpContext.Current == null) + return; + + if (HttpContext.Current.Session == null) + return; + + var sessionId = HttpContext.Current.Session.SessionID; + var sessionIdProperty = new LogEventProperty(HttpSessionIdPropertyName, new ScalarValue(sessionId)); + logEvent.AddPropertyIfAbsent(sessionIdProperty); + } + } +} diff --git a/src/Umbraco.Core/Logging/Serilog/Log4NetLevelMapperEnricher.cs b/src/Umbraco.Core/Logging/Serilog/Enrichers/Log4NetLevelMapperEnricher.cs similarity index 96% rename from src/Umbraco.Core/Logging/Serilog/Log4NetLevelMapperEnricher.cs rename to src/Umbraco.Core/Logging/Serilog/Enrichers/Log4NetLevelMapperEnricher.cs index 1424fa0b55..0c255fa8b4 100644 --- a/src/Umbraco.Core/Logging/Serilog/Log4NetLevelMapperEnricher.cs +++ b/src/Umbraco.Core/Logging/Serilog/Enrichers/Log4NetLevelMapperEnricher.cs @@ -1,7 +1,7 @@ using Serilog.Core; using Serilog.Events; -namespace Umbraco.Core.Logging.Serilog +namespace Umbraco.Core.Logging.Serilog.Enrichers { /// /// This is used to create a new property in Logs called 'Log4NetLevel' diff --git a/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs b/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs index 8861c808df..2d333ed916 100644 --- a/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs +++ b/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs @@ -3,6 +3,7 @@ using System.Web; using Serilog; using Serilog.Events; using Serilog.Formatting.Compact; +using Umbraco.Core.Logging.Serilog.Enrichers; namespace Umbraco.Core.Logging.Serilog { @@ -21,7 +22,7 @@ namespace Umbraco.Core.Logging.Serilog //Set this environment variable - so that it can be used in external config file //add key="serilog:write-to:RollingFile.pathFormat" value="%BASEDIR%\logs\log.txt" /> Environment.SetEnvironmentVariable("BASEDIR", AppDomain.CurrentDomain.BaseDirectory, EnvironmentVariableTarget.Process); - + logConfig.MinimumLevel.Verbose() //Set to highest level of logging (as any sinks may want to restrict it to Errors only) .Enrich.WithProcessId() .Enrich.WithProcessName() @@ -29,8 +30,11 @@ namespace Umbraco.Core.Logging.Serilog .Enrich.WithProperty("AppDomainId", AppDomain.CurrentDomain.Id) .Enrich.WithProperty("AppDomainAppId", HttpRuntime.AppDomainAppId.ReplaceNonAlphanumericChars(string.Empty)) .Enrich.WithProperty("MachineName", Environment.MachineName) - .Enrich.With(); - + .Enrich.With() + .Enrich.With() + .Enrich.With() + .Enrich.With(); + return logConfig; } diff --git a/src/Umbraco.Core/Manifest/ContentAppDefinitionConverter.cs b/src/Umbraco.Core/Manifest/ContentAppDefinitionConverter.cs new file mode 100644 index 0000000000..87f104d90e --- /dev/null +++ b/src/Umbraco.Core/Manifest/ContentAppDefinitionConverter.cs @@ -0,0 +1,16 @@ +using System; +using Newtonsoft.Json.Linq; +using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Serialization; + +namespace Umbraco.Core.Manifest +{ + /// + /// Implements a json read converter for . + /// + internal class ContentAppDefinitionConverter : JsonReadConverter + { + protected override IContentAppDefinition Create(Type objectType, string path, JObject jObject) + => new ManifestContentAppDefinition(); + } +} diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs new file mode 100644 index 0000000000..6b8534a88f --- /dev/null +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text.RegularExpressions; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Models.ContentEditing; + +namespace Umbraco.Core.Manifest +{ + // contentApps: [ + // { + // name: 'App Name', // required + // alias: 'appAlias', // required + // weight: 0, // optional, default is 0, use values between -99 and +99 + // icon: 'icon.app', // required + // view: 'path/view.htm', // required + // show: [ // optional, default is always show + // '-content/foo', // hide for content type 'foo' + // '+content/*', // show for all other content types + // '+media/*' // show for all media types + // ] + // }, + // ... + // ] + + /// + /// Represents a content app definition, parsed from a manifest. + /// + [DataContract(Name = "appdef", Namespace = "")] + public class ManifestContentAppDefinition : IContentAppDefinition + { + private string _view; + private ContentApp _app; + private ShowRule[] _showRules; + + /// + /// Gets or sets the name of the content app. + /// + [DataMember(Name = "name")] + public string Name { get; set; } + + /// + /// Gets or sets the unique alias of the content app. + /// + /// + /// Must be a valid javascript identifier, ie no spaces etc. + /// + [DataMember(Name = "alias")] + public string Alias { get; set; } + + /// + /// Gets or sets the weight of the content app. + /// + [DataMember(Name = "weight")] + public int Weight { get; set; } + + /// + /// Gets or sets the icon of the content app. + /// + /// + /// Must be a valid helveticons class name (see http://hlvticons.ch/). + /// + [DataMember(Name = "icon")] + public string Icon { get; set; } + + /// + /// Gets or sets the view for rendering the content app. + /// + [DataMember(Name = "view")] + public string View + { + get => _view; + set => _view = IOHelper.ResolveVirtualUrl(value); + } + + /// + /// Gets or sets the list of 'show' conditions for the content app. + /// + [DataMember(Name = "show")] + public string[] Show { get; set; } = Array.Empty(); + + /// + public ContentApp GetContentAppFor(object o) + { + string partA, partB; + + switch (o) + { + case IContent content: + partA = "content"; + partB = content.ContentType.Alias; + break; + + case IMedia media: + partA = "media"; + partB = media.ContentType.Alias; + break; + + default: + return null; + } + + var rules = _showRules ?? (_showRules = ShowRule.Parse(Show).ToArray()); + + // if no 'show' is specified, then always display the content app + if (rules.Length > 0) + { + var ok = false; + + // else iterate over each entry + foreach (var rule in rules) + { + // if the entry does not apply, skip it + if (!rule.Matches(partA, partB)) + continue; + + // if the entry applies, + // if it's an exclude entry, exit, do not display the content app + if (!rule.Show) + return null; + + // else break - ok to display + ok = true; + break; + } + + // when 'show' is specified, default is to *not* show the content app + if (!ok) + return null; + } + + // content app can be displayed + return _app ?? (_app = new ContentApp + { + Alias = Alias, + Name = Name, + Icon = Icon, + View = View, + Weight = Weight + }); + } + + private class ShowRule + { + private static readonly Regex ShowRegex = new Regex("^([+-])?([a-z]+)/([a-z0-9_]+|\\*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public bool Show { get; private set; } + public string PartA { get; private set; } + public string PartB { get; private set; } + + public bool Matches(string partA, string partB) + { + return (PartA == "*" || PartA.InvariantEquals(partA)) && (PartB == "*" || PartB.InvariantEquals(partB)); + } + + public static IEnumerable Parse(string[] rules) + { + foreach (var rule in rules) + { + var match = ShowRegex.Match(rule); + if (!match.Success) + throw new FormatException($"Illegal 'show' entry \"{rule}\" in manifest."); + + yield return new ShowRule + { + Show = match.Groups[1].Value != "-", + PartA = match.Groups[2].Value, + PartB = match.Groups[3].Value + }; + } + } + } + } +} diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index e2363e314f..125dee5c05 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Manifest @@ -98,6 +99,7 @@ namespace Umbraco.Core.Manifest var propertyEditors = new List(); var parameterEditors = new List(); var gridEditors = new List(); + var contentApps = new List(); foreach (var manifest in manifests) { @@ -106,6 +108,7 @@ namespace Umbraco.Core.Manifest if (manifest.PropertyEditors != null) propertyEditors.AddRange(manifest.PropertyEditors); if (manifest.ParameterEditors != null) parameterEditors.AddRange(manifest.ParameterEditors); if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors); + if (manifest.ContentApps != null) contentApps.AddRange(manifest.ContentApps); } return new PackageManifest @@ -114,7 +117,8 @@ namespace Umbraco.Core.Manifest Stylesheets = stylesheets.ToArray(), PropertyEditors = propertyEditors.ToArray(), ParameterEditors = parameterEditors.ToArray(), - GridEditors = gridEditors.ToArray() + GridEditors = gridEditors.ToArray(), + ContentApps = contentApps.ToArray() }; } @@ -146,7 +150,8 @@ namespace Umbraco.Core.Manifest var manifest = JsonConvert.DeserializeObject(text, new DataEditorConverter(_logger), - new ValueValidatorConverter(_validators)); + new ValueValidatorConverter(_validators), + new ContentAppDefinitionConverter()); // scripts and stylesheets are raw string, must process here for (var i = 0; i < manifest.Scripts.Length; i++) diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index a1702cc58b..32dae46a9a 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -1,5 +1,6 @@ using System; using Newtonsoft.Json; +using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Manifest @@ -23,5 +24,8 @@ namespace Umbraco.Core.Manifest [JsonProperty("gridEditors")] public GridEditor[] GridEditors { get; set; } = Array.Empty(); + + [JsonProperty("contentApps")] + public IContentAppDefinition[] ContentApps { get; set; } = Array.Empty(); } } diff --git a/src/Umbraco.Core/Media/ImageExtensions.cs b/src/Umbraco.Core/Media/ImageExtensions.cs deleted file mode 100644 index c20be13d31..0000000000 --- a/src/Umbraco.Core/Media/ImageExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Drawing; -using System.Drawing.Imaging; -using System.Linq; - -namespace Umbraco.Core.Media -{ - public static class ImageExtensions - { - /// - /// Gets the MIME type of an image. - /// - /// The image. - /// The MIME type of the image. - public static string GetMimeType(this Image image) - { - var format = image.RawFormat; - var codec = ImageCodecInfo.GetImageDecoders().First(c => c.FormatID == format.Guid); - return codec.MimeType; - } - } -} diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index ac74e6ee66..ba491fb5e1 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -69,10 +69,7 @@ namespace Umbraco.Core.Migrations.Install if (tableName.Equals(Constants.DatabaseSchema.Tables.RelationType)) CreateRelationTypeData(); - - if (tableName.Equals(Constants.DatabaseSchema.Tables.TaskType)) - CreateTaskTypeData(); - + if (tableName.Equals(Constants.DatabaseSchema.Tables.KeyValue)) CreateKeyValueData(); @@ -152,6 +149,7 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTypes, Name = "MemberTypes" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTree, Name = "MemberTree" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Domains, Name = "Domains" }); + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Languages, Name = "Languages" }); } private void CreateContentTypeData() @@ -185,7 +183,7 @@ namespace Umbraco.Core.Migrations.Install private void CreateUserGroup2AppData() { _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Content }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Developer }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Packages }); _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Media }); _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Members }); _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Settings }); @@ -232,7 +230,7 @@ namespace Umbraco.Core.Migrations.Install private void CreateLanguageData() { - _database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)" }); + _database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)", IsDefault = true }); } private void CreateContentChildTypeData() @@ -318,11 +316,6 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); } - private void CreateTaskTypeData() - { - _database.Insert(Constants.DatabaseSchema.Tables.TaskType, "id", false, new TaskTypeDto { Id = 1, Alias = "toTranslate" }); - } - private void CreateKeyValueData() { // on install, initialize the umbraco migration plan with the final state diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs index ae3f95ec4d..d45126f07f 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs @@ -59,8 +59,6 @@ namespace Umbraco.Core.Migrations.Install typeof (RelationDto), typeof (TagDto), typeof (TagRelationshipDto), - typeof (TaskTypeDto), - typeof (TaskDto), typeof (ContentType2ContentTypeDto), typeof (ContentTypeAllowedContentTypeDto), typeof (User2NodeNotifyDto), diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 48ac39a630..eeaf7533a9 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -107,16 +107,37 @@ namespace Umbraco.Core.Migrations.Upgrade Chain("{3E44F712-E2E3-473A-AE49-5D7F8E67CE3F}"); // shannon added that one - let's keep it as the default path //Chain("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}"); // stephan added that one = merge conflict, remove, - Chain("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // but it after shannon's, with a new target state, + Chain("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // but add it after shannon's, with a new target state, Add("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}", "{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // and provide a path out of the conflict state // resume at {4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4} ... Chain("{1350617A-4930-4D61-852F-E3AA9E692173}"); Chain("{39E5B1F7-A50B-437E-B768-1723AEC45B65}"); // from 7.12.0 + //Chain("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}"); // andy added that one = merge conflict, remove Chain("{0541A62B-EF87-4CA2-8225-F0EB98ECCC9F}"); // from 7.12.0 Chain("{EB34B5DC-BB87-4005-985E-D983EA496C38}"); // from 7.12.0 Chain("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}"); // from 7.12.0 Chain("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); // from 7.12.0 + //Chain("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}"); // stephan added that one = merge conflict, remove + + Chain("{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); // add andy's after others, with a new target state + From("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}") // and provide a path out of andy's + .CopyChain("{39E5B1F7-A50B-437E-B768-1723AEC45B65}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}", "{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); // to next + // resume at {8B14CEBD-EE47-4AAD-A841-93551D917F11} ... + + Chain("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // add stephan's after others, with a new target state + From("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}") // and provide a path out of stephan's + .Chain("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // to next + // resume at {5F4597F4-A4E0-4AFE-90B5-6D2F896830EB} ... + + //Chain("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}"); + Chain("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}"); + From("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}") + .Chain("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}"); + // resume at {290C18EE-B3DE-4769-84F1-1F467F3F76DA}... + + Chain("{6A2C7C1B-A9DB-4EA9-B6AB-78E7D5B722A7}"); + //FINAL diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs index d595c70fa0..7c0b26dd53 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { @@ -23,11 +24,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 private void EnsureLockObject(int id, string name) { - var db = Database; + EnsureLockObject(Database, id, name); + } + + internal static void EnsureLockObject(IUmbracoDatabase db, int id, string name) + { + // not if it already exists var exists = db.Exists(id); if (exists) return; + // be safe: delete old umbracoNode lock objects if any db.Execute($"DELETE FROM umbracoNode WHERE id={id};"); + // then create umbracoLock object db.Execute($"INSERT umbracoLock (id, name, value) VALUES ({id}, '{name}', 1);"); } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs new file mode 100644 index 0000000000..f706671022 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs @@ -0,0 +1,15 @@ +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class DropTaskTables : MigrationBase + { + public DropTaskTables(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + Delete.Table("cmsTaskType"); + Delete.Table("cmsTask"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FallbackLanguage.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FallbackLanguage.cs new file mode 100644 index 0000000000..f0d7c02b82 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FallbackLanguage.cs @@ -0,0 +1,24 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + /// + /// Adds a new, self-joined field to umbracoLanguages to hold the fall-back language for + /// a given language. + /// + public class FallbackLanguage : MigrationBase + { + public FallbackLanguage(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.Language) && x.ColumnName.InvariantEquals("fallbackLanguageId")) == false) + AddColumn("fallbackLanguageId"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs new file mode 100644 index 0000000000..aa498583ff --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs @@ -0,0 +1,79 @@ +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class RefactorVariantsModel : MigrationBase + { + public RefactorVariantsModel(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + Delete.Column("edited").FromTable(Constants.DatabaseSchema.Tables.ContentVersionCultureVariation).Do(); + + + // add available column + AddColumn("available", out var sqls); + + // so far, only those cultures that were available had records in the table + Update.Table(DocumentCultureVariationDto.TableName).Set(new { available = true }).AllRows().Do(); + + foreach (var sql in sqls) Execute.Sql(sql).Do(); + + + // add published column + AddColumn("published", out sqls); + + // make it false by default + Update.Table(DocumentCultureVariationDto.TableName).Set(new { published = false }).AllRows().Do(); + + // now figure out whether these available cultures are published, too + var getPublished = Sql() + .Select(x => x.NodeId) + .AndSelect(x => x.LanguageId) + .From() + .InnerJoin().On((node, cv) => node.NodeId == cv.NodeId) + .InnerJoin().On((cv, dv) => cv.Id == dv.Id && dv.Published) + .InnerJoin().On((cv, ccv) => cv.Id == ccv.VersionId); + + foreach (var dto in Database.Fetch(getPublished)) + Database.Execute(Sql() + .Update(u => u.Set(x => x.Published, true)) + .Where(x => x.NodeId == dto.NodeId && x.LanguageId == dto.LanguageId)); + + foreach (var sql in sqls) Execute.Sql(sql).Do(); + + // so far, it was kinda impossible to make a culture unavailable again, + // so we *should* not have anything published but not available - ignore + + + // add name column + AddColumn("name"); + + // so far, every record in the table mapped to an available culture + var getNames = Sql() + .Select(x => x.NodeId) + .AndSelect(x => x.LanguageId, x => x.Name) + .From() + .InnerJoin().On((node, cv) => node.NodeId == cv.NodeId && cv.Current) + .InnerJoin().On((cv, ccv) => cv.Id == ccv.VersionId); + + foreach (var dto in Database.Fetch(getNames)) + Database.Execute(Sql() + .Update(u => u.Set(x => x.Name, dto.Name)) + .Where(x => x.NodeId == dto.NodeId && x.LanguageId == dto.LanguageId)); + } + + // ReSharper disable once ClassNeverInstantiated.Local + // ReSharper disable UnusedAutoPropertyAccessor.Local + private class TempDto + { + public int NodeId { get; set; } + public int LanguageId { get; set; } + public string Name { get; set; } + } + // ReSharper restore UnusedAutoPropertyAccessor.Local + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdateDefaultMandatoryLanguage.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdateDefaultMandatoryLanguage.cs new file mode 100644 index 0000000000..b965bc71d2 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdateDefaultMandatoryLanguage.cs @@ -0,0 +1,48 @@ +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class UpdateDefaultMandatoryLanguage : MigrationBase + { + public UpdateDefaultMandatoryLanguage(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // add the new languages lock object + AddLockObjects.EnsureLockObject(Database, Constants.Locks.Languages, "Languages"); + + // get all existing languages + var selectDtos = Sql() + .Select() + .From(); + + var dtos = Database.Fetch(selectDtos); + + // get the id of the language which is already the default one, if any, + // else get the lowest language id, which will become the default language + var defaultId = int.MaxValue; + foreach (var dto in dtos) + { + if (dto.IsDefault) + { + defaultId = dto.Id; + break; + } + + if (dto.Id < defaultId) defaultId = dto.Id; + } + + // update, so that language with that id is now default and mandatory + var updateDefault = Sql() + .Update(u => u + .Set(x => x.IsDefault, true) + .Set(x => x.IsMandatory, true)) + .Where(x => x.Id == defaultId); + + Database.Execute(updateDefault); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs index 174404b1b9..9e223e7beb 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs @@ -324,9 +324,7 @@ WHERE v1.propertyTypeId=v2.propertyTypeId AND v1.languageId=v2.languageId AND v1 public const string Tag = "cmsTags"; public const string TagRelationship = "cmsTagRelationship"; - - public const string Task = "cmsTask"; - public const string TaskType = "cmsTaskType"; + // ReSharper restore UnusedMember.Local } } diff --git a/src/Umbraco.Core/Models/AuditType.cs b/src/Umbraco.Core/Models/AuditType.cs index da2c8e5d8e..a5ae34a89d 100644 --- a/src/Umbraco.Core/Models/AuditType.cs +++ b/src/Umbraco.Core/Models/AuditType.cs @@ -32,7 +32,7 @@ /// /// Used when nodes are unpublished /// - UnPublish, + Unpublish, /// /// Used when nodes are moved /// diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index dd379e02f8..238d87b186 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -269,7 +269,7 @@ namespace Umbraco.Core.Models if (_publishInfos == null) _publishInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); - _publishInfos[culture] = (name, date); + _publishInfos[culture.ToLowerInvariant()] = (name, date); } private void ClearPublishInfos() @@ -294,7 +294,7 @@ namespace Umbraco.Core.Models throw new ArgumentNullOrEmptyException(nameof(culture)); if (_editedCultures == null) _editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase); - _editedCultures.Add(culture); + _editedCultures.Add(culture.ToLowerInvariant()); } // sets all publish edited diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index d5d9fcfdac..bf2fd580d9 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -167,7 +167,7 @@ namespace Umbraco.Core.Models } /// - public DateTime? GetCultureDate(string culture) + public DateTime? GetUpdateDate(string culture) { if (culture.IsNullOrWhiteSpace()) return null; if (!ContentTypeBase.VariesByCulture()) return null; @@ -202,6 +202,12 @@ namespace Umbraco.Core.Models } } + internal void TouchCulture(string culture) + { + if (ContentTypeBase.VariesByCulture() && _cultureInfos != null && _cultureInfos.TryGetValue(culture, out var infos)) + _cultureInfos[culture] = (infos.Name, DateTime.Now); + } + protected void ClearCultureInfos() { _cultureInfos = null; @@ -280,26 +286,6 @@ namespace Umbraco.Core.Models Properties.Add(property); } - // HttpPostedFileBase is the base class that can be mocked - // HttpPostedFile is what we get in ASP.NET - // HttpPostedFileWrapper wraps sealed HttpPostedFile as HttpPostedFileBase - - /// - /// Sets the posted file value of a property. - /// - public virtual void SetValue(string propertyTypeAlias, HttpPostedFile value, string culture = null, string segment = null) - { - ContentExtensions.SetValue(this, propertyTypeAlias, new HttpPostedFileWrapper(value), culture, segment); - } - - /// - /// Sets the posted file value of a property. - /// - public virtual void SetValue(string propertyTypeAlias, HttpPostedFileBase value, string culture = null, string segment = null) - { - ContentExtensions.SetValue(this, propertyTypeAlias, value, culture, segment); - } - #endregion #region Copy diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs b/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs new file mode 100644 index 0000000000..bf28c28c9e --- /dev/null +++ b/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs @@ -0,0 +1,72 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models.ContentEditing +{ + /// + /// Represents a content app. + /// + /// + /// Content apps are editor extensions. + /// + [DataContract(Name = "app", Namespace = "")] + public class ContentApp + { + /// + /// Gets the name of the content app. + /// + [DataMember(Name = "name")] + public string Name { get; set; } + + /// + /// Gets the unique alias of the content app. + /// + /// + /// Must be a valid javascript identifier, ie no spaces etc. + /// + [DataMember(Name = "alias")] + public string Alias { get; set; } + + /// + /// Gets or sets the weight of the content app. + /// + /// + /// Content apps are ordered by weight, from left (lowest values) to right (highest values). + /// Some built-in apps have special weights: listview is -666, content is -100 and infos is +100. + /// The default weight is 0, meaning somewhere in-between content and infos, but weight could + /// be used for ordering between user-level apps, or anything really. + /// + [DataMember(Name = "weight")] + public int Weight { get; set; } + + /// + /// Gets the icon of the content app. + /// + /// + /// Must be a valid helveticons class name (see http://hlvticons.ch/). + /// + [DataMember(Name = "icon")] + public string Icon { get; set; } + + /// + /// Gets the view for rendering the content app. + /// + [DataMember(Name = "view")] + public string View { get; set; } + + /// + /// The view model specific to this app + /// + [DataMember(Name = "viewModel")] + public object ViewModel { get; set; } + + /// + /// Gets a value indicating whether the app is active. + /// + /// + /// Normally reserved for Angular to deal with but in some cases this can be set on the server side. + /// + [DataMember(Name = "active")] + public bool Active { get; set; } + } +} + diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs b/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs new file mode 100644 index 0000000000..5e0c421742 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Core.Models.ContentEditing +{ + /// + /// Represents a content app definition. + /// + public interface IContentAppDefinition + { + /// + /// Gets the content app for an object. + /// + /// The source object. + /// The content app for the object, or null. + /// + /// The definition must determine, based on , whether + /// the content app should be displayed or not, and return either a + /// instance, or null. + /// + ContentApp GetContentAppFor(object source); + } +} diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index ff647dc189..e6439acade 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -98,6 +98,30 @@ namespace Umbraco.Core.Models } } + /// + /// Determines if AllowedTemplates contains templateId + /// + /// The template id to check + /// True if AllowedTemplates contains the templateId else False + public bool IsAllowedTemplate(int templateId) + { + return AllowedTemplates == null + ? false + : AllowedTemplates.Any(t => t.Id == templateId); + } + + /// + /// Determines if AllowedTemplates contains templateId + /// + /// The template alias to check + /// True if AllowedTemplates contains the templateAlias else False + public bool IsAllowedTemplate(string templateAlias) + { + return AllowedTemplates == null + ? false + : AllowedTemplates.Any(t => t.Alias.Equals(templateAlias, StringComparison.InvariantCultureIgnoreCase)); + } + /// /// Sets the default template for the ContentType /// diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs index 9668864588..4f0d0d6c31 100644 --- a/src/Umbraco.Core/Models/DataType.cs +++ b/src/Umbraco.Core/Models/DataType.cs @@ -30,6 +30,9 @@ namespace Umbraco.Core.Models { _editor = editor ?? throw new ArgumentNullException(nameof(editor)); ParentId = parentId; + + // set a default configuration + Configuration = _editor.GetConfigurationEditor().DefaultConfigurationObject; } private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); diff --git a/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs index 5b63ad81c5..39ece5fa10 100644 --- a/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace Umbraco.Core.Models.Entities { @@ -8,13 +9,32 @@ namespace Umbraco.Core.Models.Entities public class DocumentEntitySlim : ContentEntitySlim, IDocumentEntitySlim { private static readonly IReadOnlyDictionary Empty = new Dictionary(); + private IReadOnlyDictionary _cultureNames; + private IEnumerable _publishedCultures; + private IEnumerable _editedCultures; + + /// public IReadOnlyDictionary CultureNames { get => _cultureNames ?? Empty; set => _cultureNames = value; } + /// + public IEnumerable PublishedCultures + { + get => _publishedCultures ?? Enumerable.Empty(); + set => _publishedCultures = value; + } + + /// + public IEnumerable EditedCultures + { + get => _editedCultures ?? Enumerable.Empty(); + set => _editedCultures = value; + } + public ContentVariation Variations { get; set; } /// @@ -22,5 +42,6 @@ namespace Umbraco.Core.Models.Entities /// public bool Edited { get; set; } + } } diff --git a/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs index dc986a4cd9..9ab557b02c 100644 --- a/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs @@ -20,4 +20,4 @@ /// string ContentTypeThumbnail { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs index 6b72fd4a2b..38fd9a02f1 100644 --- a/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs @@ -7,26 +7,35 @@ namespace Umbraco.Core.Models.Entities /// public interface IDocumentEntitySlim : IContentEntitySlim { - //fixme we need to supply more information than this and change this property name. This will need to include Published/Editor per variation since we need this information for the tree + /// + /// Gets the variant name for each culture + /// IReadOnlyDictionary CultureNames { get; } + /// + /// Gets the published cultures. + /// + IEnumerable PublishedCultures { get; } + + /// + /// Gets the edited cultures. + /// + IEnumerable EditedCultures { get; } + + /// + /// Gets the content variation of the content type. + /// ContentVariation Variations { get; } /// - /// At least one variation is published + /// Gets a value indicating whether the content is published. /// - /// - /// If the document is invariant, this simply means there is a published version - /// - bool Published { get; set; } + bool Published { get; } /// - /// At least one variation has pending changes + /// Gets a value indicating whether the content has been edited. /// - /// - /// If the document is invariant, this simply means there is pending changes - /// - bool Edited { get; set; } + bool Edited { get; } } } diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index 5910d01d34..d9bc32aaf0 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -172,10 +172,9 @@ namespace Umbraco.Core.Models /// /// A value indicating whether the culture can be published. /// - /// Fails if values cannot be published, e.g. if some values are not valid. + /// Fails if properties don't pass variant validtion rules. /// Publishing must be finalized via the content service SavePublishing method. /// - // fixme - should return an attempt with error results bool PublishCulture(string culture = "*"); /// diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index 2cf2d85024..460bd521d4 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -80,13 +80,13 @@ namespace Umbraco.Core.Models bool IsCultureAvailable(string culture); /// - /// Gets the date a culture was created. + /// Gets the date a culture was updated. /// /// /// When is null, returns null. /// If the specified culture is not available, returns null. /// - DateTime? GetCultureDate(string culture); + DateTime? GetUpdateDate(string culture); /// /// List of properties, which make up all the data available for this Content object @@ -139,7 +139,7 @@ namespace Umbraco.Core.Models // fixme validate published cultures? /// - /// Validates the content item's properties. + /// Validates the content item's properties pass variant rules /// /// If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor /// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty. diff --git a/src/Umbraco.Core/Models/IContentType.cs b/src/Umbraco.Core/Models/IContentType.cs index 951a685d22..99fffdca7b 100644 --- a/src/Umbraco.Core/Models/IContentType.cs +++ b/src/Umbraco.Core/Models/IContentType.cs @@ -17,6 +17,20 @@ namespace Umbraco.Core.Models /// IEnumerable AllowedTemplates { get; set; } + /// + /// Determines if AllowedTemplates contains templateId + /// + /// The template id to check + /// True if AllowedTemplates contains the templateId else False + bool IsAllowedTemplate(int templateId); + + /// + /// Determines if AllowedTemplates contains templateId + /// + /// The template alias to check + /// True if AllowedTemplates contains the templateAlias else False + bool IsAllowedTemplate(string templateAlias); + /// /// Sets the default template for the ContentType /// diff --git a/src/Umbraco.Core/Models/ILanguage.cs b/src/Umbraco.Core/Models/ILanguage.cs index 7bf9e9b32c..c0d2fed839 100644 --- a/src/Umbraco.Core/Models/ILanguage.cs +++ b/src/Umbraco.Core/Models/ILanguage.cs @@ -4,34 +4,54 @@ using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { + /// + /// Represents a language. + /// public interface ILanguage : IEntity, IRememberBeingDirty { /// - /// Gets or sets the Iso Code for the Language + /// Gets or sets the ISO code of the language. /// [DataMember] string IsoCode { get; set; } /// - /// Gets or sets the Culture Name for the Language + /// Gets or sets the culture name of the language. /// [DataMember] string CultureName { get; set; } /// - /// Returns a object for the current Language + /// Gets the object for the language. /// [IgnoreDataMember] CultureInfo CultureInfo { get; } /// - /// Defines if this language is the default variant language when language variants are in use + /// Gets or sets a value indicating whether the language is the default language. /// - bool IsDefaultVariantLanguage { get; set; } + [DataMember] + bool IsDefault { get; set; } /// - /// If true, a variant node cannot be published unless this language variant is created + /// Gets or sets a value indicating whether the language is mandatory. /// - bool Mandatory { get; set; } + /// + /// When a language is mandatory, a multi-lingual document cannot be published + /// without that language being published, and unpublishing that language unpublishes + /// the entire document. + /// + [DataMember] + bool IsMandatory { get; set; } + + /// + /// Gets or sets the identifier of a fallback language. + /// + /// + /// The fallback language can be used in multi-lingual scenarios, to help + /// define fallback strategies when a value does not exist for a requested language. + /// + [DataMember] + int? FallbackLanguageId { get; set; } } } diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index b4d9bb2c8a..23c232324a 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -93,14 +93,6 @@ namespace Umbraco.Core.Models.Identity _roles.CollectionChanged += _roles_CollectionChanged; } - public virtual async Task GenerateUserIdentityAsync(BackOfficeUserManager manager) - { - // NOTE the authenticationType must match the umbraco one - // defined in CookieAuthenticationOptions.AuthenticationType - var userIdentity = await manager.CreateIdentityAsync(this, Constants.Security.BackOfficeAuthenticationType); - return userIdentity; - } - /// /// Returns true if an Id has been set on this object this will be false if the object is new and not peristed to the database /// diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index fa1c9dc826..940648c4b9 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -19,6 +19,7 @@ namespace Umbraco.Core.Models private string _cultureName; private bool _isDefaultVariantLanguage; private bool _mandatory; + private int? _fallbackLanguageId; public Language(string isoCode) { @@ -30,13 +31,12 @@ namespace Umbraco.Core.Models { public readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo(x => x.IsoCode); public readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo(x => x.CultureName); - public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDefaultVariantLanguage); - public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo(x => x.Mandatory); + public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDefault); + public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo(x => x.IsMandatory); + public readonly PropertyInfo FallbackLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.FallbackLanguageId); } - /// - /// Gets or sets the Iso Code for the Language - /// + /// [DataMember] public string IsoCode { @@ -44,9 +44,7 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _isoCode, Ps.Value.IsoCodeSelector); } - /// - /// Gets or sets the Culture Name for the Language - /// + /// [DataMember] public string CultureName { @@ -54,22 +52,29 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _cultureName, Ps.Value.CultureNameSelector); } - /// - /// Returns a object for the current Language - /// + /// [IgnoreDataMember] public CultureInfo CultureInfo => CultureInfo.GetCultureInfo(IsoCode); - public bool IsDefaultVariantLanguage + /// + public bool IsDefault { get => _isDefaultVariantLanguage; set => SetPropertyValueAndDetectChanges(value, ref _isDefaultVariantLanguage, Ps.Value.IsDefaultVariantLanguageSelector); } - public bool Mandatory + /// + public bool IsMandatory { get => _mandatory; set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector); } + + /// + public int? FallbackLanguageId + { + get => _fallbackLanguageId; + set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageId, Ps.Value.FallbackLanguageSelector); + } } } diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 561eb0f3cd..9066674193 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -260,7 +260,7 @@ namespace Umbraco.Core.Models.Membership { get { - if (LastLoginDate == default(DateTime) && IsApproved == false && InvitedDate != null) + if (LastLoginDate == default && IsApproved == false && InvitedDate != null) return UserState.Invited; if (IsLockedOut) @@ -268,6 +268,10 @@ namespace Umbraco.Core.Models.Membership if (IsApproved == false) return UserState.Disabled; + // User is not disabled or locked and has never logged in before + if (LastLoginDate == default && IsApproved && IsLockedOut == false) + return UserState.Inactive; + return UserState.Active; } } diff --git a/src/Umbraco.Core/Models/Membership/UserState.cs b/src/Umbraco.Core/Models/Membership/UserState.cs index 0f8d0ec030..fc277b4fa3 100644 --- a/src/Umbraco.Core/Models/Membership/UserState.cs +++ b/src/Umbraco.Core/Models/Membership/UserState.cs @@ -9,6 +9,7 @@ Active = 0, Disabled = 1, LockedOut = 2, - Invited = 3 + Invited = 3, + Inactive = 4 } } diff --git a/src/Umbraco.Core/Models/PropertyTagChange.cs b/src/Umbraco.Core/Models/PropertyTagChange.cs deleted file mode 100644 index 5c911f4c30..0000000000 --- a/src/Umbraco.Core/Models/PropertyTagChange.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Models -{ - /// - /// A set of tag changes. - /// - internal class PropertyTagChange - { - public ChangeType Type { get; set; } - - public IEnumerable<(string Type, string Tags)> Tags { get; set; } - - public enum ChangeType - { - Replace, - Remove, - Merge - } - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/Fallback.cs b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs new file mode 100644 index 0000000000..0434218555 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Manages the built-in fallback policies. + /// + public struct Fallback : IEnumerable + { + private readonly int[] _values; + + /// + /// Initializes a new instance of the struct with values. + /// + private Fallback(int[] values) + { + _values = values; + } + + /// + /// Gets an ordered set of fallback policies. + /// + /// + public static Fallback To(params int[] values) => new Fallback(values); + + /// + /// Do not fallback. + /// + public const int None = 0; + + /// + /// Fallback to default value. + /// + public const int DefaultValue = 1; + + /// + /// Gets the fallback to default value policy. + /// + public static Fallback ToDefaultValue => new Fallback(new[] { DefaultValue }); + + /// + /// Fallback to other languages. + /// + public const int Language = 2; + + /// + /// Gets the fallback to language policy. + /// + public static Fallback ToLanguage => new Fallback(new[] { Language }); + + /// + /// Fallback to tree ancestors. + /// + public const int Ancestors = 3; + + /// + /// Gets the fallback to tree ancestors policy. + /// + public static Fallback ToAncestors => new Fallback(new[] { Ancestors }); + + /// + public IEnumerator GetEnumerator() + { + return ((IEnumerable)_values ?? Array.Empty()).GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs index 8e1dcfd543..f30a53c8b6 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs @@ -1,37 +1,122 @@ -using Umbraco.Core.Composing; - -namespace Umbraco.Core.Models.PublishedContent +namespace Umbraco.Core.Models.PublishedContent { /// /// Provides a fallback strategy for getting values. /// - // fixme - IPublishedValueFallback is still WorkInProgress - // todo - properly document methods, etc - // todo - understand caching vs fallback (recurse etc) public interface IPublishedValueFallback { - // note that at property level, property.GetValue() does NOT implement fallback, and one has - // to get property.Value() or property.Value() to trigger fallback + /// + /// Tries to get a fallback value for a property. + /// + /// The property. + /// The requested culture. + /// The requested segment. + /// A fallback strategy. + /// An optional default value. + /// The fallback value. + /// A value indicating whether a fallback value could be provided. + /// + /// This method is called whenever property.Value(culture, segment, defaultValue) is called, and + /// property.HasValue(culture, segment) is false. + /// It can only fallback at property level (no recurse). + /// At property level, property.GetValue() does *not* implement fallback, and one has to + /// get property.Value() or property.Value{T}() to trigger fallback. + /// Note that and may not be contextualized, + /// so the variant context should be used to contextualize them (see our default implementation in + /// the web project. + /// + bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value); - // this method is called whenever property.Value(culture, segment, defaultValue) is called, and - // property.HasValue(culture, segment) is false. it can only fallback at property level (no recurse). + /// + /// Tries to get a fallback value for a property. + /// + /// The type of the value. + /// The property. + /// The requested culture. + /// The requested segment. + /// A fallback strategy. + /// An optional default value. + /// The fallback value. + /// A value indicating whether a fallback value could be provided. + /// + /// This method is called whenever property.Value{T}(culture, segment, defaultValue) is called, and + /// property.HasValue(culture, segment) is false. + /// It can only fallback at property level (no recurse). + /// At property level, property.GetValue() does *not* implement fallback, and one has to + /// get property.Value() or property.Value{T}() to trigger fallback. + /// + bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value); - object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue); + /// + /// Tries to get a fallback value for a published element property. + /// + /// The published element. + /// The property alias. + /// The requested culture. + /// The requested segment. + /// A fallback strategy. + /// An optional default value. + /// The fallback value. + /// A value indicating whether a fallback value could be provided. + /// + /// This method is called whenever getting the property value for the specified alias, culture and + /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false. + /// It can only fallback at element level (no recurse). + /// + bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value); - // this method is called whenever property.Value(culture, segment, defaultValue) is called, and - // property.HasValue(culture, segment) is false. it can only fallback at property level (no recurse). + /// + /// Tries to get a fallback value for a published element property. + /// + /// The type of the value. + /// The published element. + /// The property alias. + /// The requested culture. + /// The requested segment. + /// A fallback strategy. + /// An optional default value. + /// The fallback value. + /// A value indicating whether a fallback value could be provided. + /// + /// This method is called whenever getting the property value for the specified alias, culture and + /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false. + /// It can only fallback at element level (no recurse). + /// + bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value); - T GetValue(IPublishedProperty property, string culture, string segment, T defaultValue); + /// + /// Tries to get a fallback value for a published content property. + /// + /// The published element. + /// The property alias. + /// The requested culture. + /// The requested segment. + /// A fallback strategy. + /// An optional default value. + /// The fallback value. + /// A value indicating whether a fallback value could be provided. + /// + /// This method is called whenever getting the property value for the specified alias, culture and + /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false. + /// + bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value); - // these methods to be called whenever getting the property value for the specified alias, culture and segment, - // either returned no property at all, or a property that does not HasValue for the specified culture and segment. - - object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue); - - T GetValue(IPublishedElement content, string alias, string culture, string segment, T defaultValue); - - object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse); - - T GetValue(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse); + /// + /// Tries to get a fallback value for a published content property. + /// + /// The type of the value. + /// The published element. + /// The property alias. + /// The requested culture. + /// The requested segment. + /// A fallback strategy. + /// An optional default value. + /// The fallback value. + /// A value indicating whether a fallback value could be provided. + /// + /// This method is called whenever getting the property value for the specified alias, culture and + /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false. + /// + bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs index 962148e138..a366742cc5 100644 --- a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs @@ -49,17 +49,24 @@ namespace Umbraco.Core.Models.PublishedContent /// The model types map. /// The actual Clr type. public static Type Map(Type type, Dictionary modelTypes) + => Map(type, modelTypes, false); + + public static Type Map(Type type, Dictionary modelTypes, bool dictionaryIsInvariant) { + // it may be that senders forgot to send an invariant dictionary (garbage-in) + if (!dictionaryIsInvariant) + modelTypes = new Dictionary(modelTypes, StringComparer.InvariantCultureIgnoreCase); + if (type is ModelType modelType) { - if (modelTypes.TryGetValue(modelType.ContentTypeAlias, out Type actualType)) + if (modelTypes.TryGetValue(modelType.ContentTypeAlias, out var actualType)) return actualType; throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{modelType.ContentTypeAlias}\"."); } if (type is ModelTypeArrayType arrayType) { - if (modelTypes.TryGetValue(arrayType.ContentTypeAlias, out Type actualType)) + if (modelTypes.TryGetValue(arrayType.ContentTypeAlias, out var actualType)) return actualType.MakeArrayType(); throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{arrayType.ContentTypeAlias}\"."); } @@ -70,7 +77,7 @@ namespace Umbraco.Core.Models.PublishedContent if (def == null) throw new InvalidOperationException("panic"); - var args = type.GetGenericArguments().Select(x => Map(x, modelTypes)).ToArray(); + var args = type.GetGenericArguments().Select(x => Map(x, modelTypes, true)).ToArray(); return def.MakeGenericType(args); } @@ -81,7 +88,14 @@ namespace Umbraco.Core.Models.PublishedContent /// The model types map. /// The actual Clr type name. public static string MapToName(Type type, Dictionary map) + => MapToName(type, map, false); + + private static string MapToName(Type type, Dictionary map, bool dictionaryIsInvariant) { + // it may be that senders forgot to send an invariant dictionary (garbage-in) + if (!dictionaryIsInvariant) + map = new Dictionary(map, StringComparer.InvariantCultureIgnoreCase); + if (type is ModelType modelType) { if (map.TryGetValue(modelType.ContentTypeAlias, out var actualTypeName)) @@ -102,7 +116,7 @@ namespace Umbraco.Core.Models.PublishedContent if (def == null) throw new InvalidOperationException("panic"); - var args = type.GetGenericArguments().Select(x => MapToName(x, map)).ToArray(); + var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray(); var defFullName = def.FullName.Substring(0, def.FullName.IndexOf('`')); return defFullName + "<" + string.Join(", ", args) + ">"; } diff --git a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs index b99b4ad415..cd7b063d44 100644 --- a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs @@ -9,21 +9,45 @@ public class NoopPublishedValueFallback : IPublishedValueFallback { /// - public object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue) => defaultValue; + public bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value) + { + value = default; + return false; + } /// - public T GetValue(IPublishedProperty property, string culture, string segment, T defaultValue) => defaultValue; + public bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value) + { + value = default; + return false; + } /// - public object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue) => defaultValue; + public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value) + { + value = default; + return false; + } /// - public T GetValue(IPublishedElement content, string alias, string culture, string segment, T defaultValue) => defaultValue; + public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value) + { + value = default; + return false; + } /// - public object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse) => defaultValue; + public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value) + { + value = default; + return false; + } /// - public T GetValue(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse) => defaultValue; + public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value) + { + value = default; + return false; + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs index e0514b38d7..882109f908 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs @@ -6,7 +6,7 @@ /// /// Every strongly-typed property set class should inherit from PublishedElementModel /// (or inherit from a class that inherits from... etc.) so they are picked by the factory. - public class PublishedElementModel : PublishedElementWrapped + public abstract class PublishedElementModel : PublishedElementWrapped { /// /// diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs new file mode 100644 index 0000000000..6710d79cc6 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs @@ -0,0 +1,15 @@ +namespace Umbraco.Core.Models.PublishedContent +{ + public static class VariationContextAccessorExtensions + { + public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string culture, ref string segment) + { + if (culture != null && segment != null) return; + + // use context values + var publishedVariationContext = variationContextAccessor?.VariationContext; + if (culture == null) culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : ""; + if (segment == null) segment = variations.VariesBySegment() ? publishedVariationContext?.Segment : ""; + } + } +} diff --git a/src/Umbraco.Core/Models/Section.cs b/src/Umbraco.Core/Models/Section.cs index cbd9dfe7dc..4b7f8309dd 100644 --- a/src/Umbraco.Core/Models/Section.cs +++ b/src/Umbraco.Core/Models/Section.cs @@ -1,26 +1,22 @@ namespace Umbraco.Core.Models { /// - /// Represents a section defined in the app.config file + /// Represents a section defined in the app.config file. /// public class Section { - public Section(string name, string @alias, string icon, int sortOrder) + public Section(string name, string @alias, int sortOrder) { Name = name; Alias = alias; - Icon = icon; SortOrder = sortOrder; } public Section() - { - - } + { } public string Name { get; set; } public string Alias { get; set; } - public string Icon { get; set; } public int SortOrder { get; set; } } } diff --git a/src/Umbraco.Core/Models/Task.cs b/src/Umbraco.Core/Models/Task.cs deleted file mode 100644 index cb35813619..0000000000 --- a/src/Umbraco.Core/Models/Task.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Task - /// - [Serializable] - [DataContract(IsReference = true)] - public class Task : EntityBase - { - private bool _closed; - private TaskType _taskType; - private int _entityId; - private int _ownerUserId; - private int _assigneeUserId; - private string _comment; - - public Task(TaskType taskType) - { - _taskType = taskType; - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo ClosedSelector = ExpressionHelper.GetPropertyInfo(x => x.Closed); - public readonly PropertyInfo TaskTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.TaskType); - public readonly PropertyInfo EntityIdSelector = ExpressionHelper.GetPropertyInfo(x => x.EntityId); - public readonly PropertyInfo OwnerUserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.OwnerUserId); - public readonly PropertyInfo AssigneeUserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.AssigneeUserId); - public readonly PropertyInfo CommentSelector = ExpressionHelper.GetPropertyInfo(x => x.Comment); - } - - /// - /// Gets or sets a boolean indicating whether the task is closed - /// - [DataMember] - public bool Closed - { - get { return _closed; } - set { SetPropertyValueAndDetectChanges(value, ref _closed, Ps.Value.ClosedSelector); } - } - - /// - /// Gets or sets the TaskType of the Task - /// - [DataMember] - public TaskType TaskType - { - get { return _taskType; } - set { SetPropertyValueAndDetectChanges(value, ref _taskType, Ps.Value.TaskTypeSelector); } - } - - /// - /// Gets or sets the Id of the entity, which this task is associated to - /// - [DataMember] - public int EntityId - { - get { return _entityId; } - set { SetPropertyValueAndDetectChanges(value, ref _entityId, Ps.Value.EntityIdSelector); } - } - - /// - /// Gets or sets the Id of the user, who owns this task - /// - [DataMember] - public int OwnerUserId - { - get { return _ownerUserId; } - set { SetPropertyValueAndDetectChanges(value, ref _ownerUserId, Ps.Value.OwnerUserIdSelector); } - } - - /// - /// Gets or sets the Id of the user, who is assigned to this task - /// - [DataMember] - public int AssigneeUserId - { - get { return _assigneeUserId; } - set { SetPropertyValueAndDetectChanges(value, ref _assigneeUserId, Ps.Value.AssigneeUserIdSelector); } - } - - /// - /// Gets or sets the Comment for the Task - /// - [DataMember] - public string Comment - { - get { return _comment; } - set { SetPropertyValueAndDetectChanges(value, ref _comment, Ps.Value.CommentSelector); } - } - - } -} diff --git a/src/Umbraco.Core/Models/TaskType.cs b/src/Umbraco.Core/Models/TaskType.cs deleted file mode 100644 index f5e6621239..0000000000 --- a/src/Umbraco.Core/Models/TaskType.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.Entities; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a Task Type - /// - [Serializable] - [DataContract(IsReference = true)] - public class TaskType : EntityBase - { - private string _alias; - - public TaskType(string @alias) - { - _alias = alias; - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - } - - /// - /// Gets or sets the Alias of the TaskType - /// - [DataMember] - public string Alias - { - get { return _alias; } - set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } - } - } -} diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 9cdbee1951..b7e67c45ea 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -1,14 +1,6 @@ using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; using System.Reflection; using System.Runtime.Serialization; -using System.Text; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; -using Umbraco.Core.Services; using Umbraco.Core.Strings; namespace Umbraco.Core.Models diff --git a/src/Umbraco.Core/NetworkHelper.cs b/src/Umbraco.Core/NetworkHelper.cs index e5e153966a..8f310ccf0c 100644 --- a/src/Umbraco.Core/NetworkHelper.cs +++ b/src/Umbraco.Core/NetworkHelper.cs @@ -10,9 +10,6 @@ namespace Umbraco.Core /// /// Returns the machine name that is safe to use in file paths. /// - /// - /// see: https://github.com/Shandem/ClientDependency/issues/4 - /// public static string FileSafeMachineName { get { return MachineName.ReplaceNonAlphanumericChars('-'); } diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs index 79dd45b73b..523d09c1f7 100644 --- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs +++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs @@ -69,10 +69,7 @@ namespace Umbraco.Core public const string Tag = /*TableNamePrefix*/ "cms" + "Tags"; public const string TagRelationship = /*TableNamePrefix*/ "cms" + "TagRelationship"; - - public const string Task = /*TableNamePrefix*/ "cms" + "Task"; - public const string TaskType = /*TableNamePrefix*/ "cms" + "TaskType"; - + public const string KeyValue = TableNamePrefix + "KeyValue"; public const string AuditEntry = /*TableNamePrefix*/ "umbraco" + "Audit"; diff --git a/src/Umbraco.Core/Persistence/Constants-Locks.cs b/src/Umbraco.Core/Persistence/Constants-Locks.cs index 6f5d4bb0dc..1dcd2408e7 100644 --- a/src/Umbraco.Core/Persistence/Constants-Locks.cs +++ b/src/Umbraco.Core/Persistence/Constants-Locks.cs @@ -3,17 +3,60 @@ namespace Umbraco.Core { static partial class Constants { + /// + /// Defines lock objects. + /// public static class Locks { + /// + /// All servers. + /// public const int Servers = -331; + + /// + /// All content and media types. + /// public const int ContentTypes = -332; + + /// + /// The entire content tree, i.e. all content items. + /// public const int ContentTree = -333; + + /// + /// The entire media tree, i.e. all media items. + /// public const int MediaTree = -334; + + /// + /// The entire member tree, i.e. all members. + /// public const int MemberTree = -335; + + /// + /// All media types. + /// public const int MediaTypes = -336; + + /// + /// All member types. + /// public const int MemberTypes = -337; + + /// + /// All domains. + /// public const int Domains = -338; + + /// + /// All key-values. + /// public const int KeyValues = -339; + + /// + /// All languages. + /// + public const int Languages = -340; } } } diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs index a4e51b913e..1dca820a6c 100644 --- a/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Persistence.Dtos internal class ContentVersionCultureVariationDto { public const string TableName = Constants.DatabaseSchema.Tables.ContentVersionCultureVariation; - private int? _publishedUserId; + private int? _updateUserId; [Column("id")] [PrimaryKeyColumn] @@ -33,16 +33,12 @@ namespace Umbraco.Core.Persistence.Dtos [Column("name")] public string Name { get; set; } - [Column("date")] - public DateTime Date { get; set; } + [Column("date")] // fixme: db rename to 'updateDate' + public DateTime UpdateDate { get; set; } - // fixme want? - [Column("availableUserId")] + [Column("availableUserId")] // fixme: db rename to 'updateDate' [ForeignKey(typeof(UserDto))] [NullSetting(NullSetting = NullSettings.Null)] - public int? PublishedUserId { get => _publishedUserId == 0 ? null : _publishedUserId; set => _publishedUserId = value; } //return null if zero - - [Column("edited")] - public bool Edited { get; set; } + public int? UpdateUserId { get => _updateUserId == 0 ? null : _updateUserId; set => _updateUserId = value; } //return null if zero } } diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs index 3ec40c74b3..a13bf921d9 100644 --- a/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs @@ -21,11 +21,11 @@ namespace Umbraco.Core.Persistence.Dtos [ForeignKey(typeof(ContentDto))] public int NodeId { get; set; } - [Column("versionDate")] + [Column("versionDate")] // fixme: db rename to 'updateDate' [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime VersionDate { get; set; } - [Column("userId")] + [Column("userId")] // fixme: db rename to 'updateUserId' [ForeignKey(typeof(UserDto))] [NullSetting(NullSetting = NullSettings.Null)] public int? UserId { get => _userId == 0 ? null : _userId; set => _userId = value; } //return null if zero diff --git a/src/Umbraco.Core/Persistence/Dtos/DocumentCultureVariationDto.cs b/src/Umbraco.Core/Persistence/Dtos/DocumentCultureVariationDto.cs index 78e819e714..ed61ea5622 100644 --- a/src/Umbraco.Core/Persistence/Dtos/DocumentCultureVariationDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/DocumentCultureVariationDto.cs @@ -28,7 +28,25 @@ namespace Umbraco.Core.Persistence.Dtos [Ignore] public string Culture { get; set; } + // authority on whether a culture has been edited [Column("edited")] public bool Edited { get; set; } + + // de-normalized for perfs + // (means there is a current content version culture variation for the language) + [Column("available")] + public bool Available { get; set; } + + // de-normalized for perfs + // (means there is a published content version culture variation for the language) + [Column("published")] + public bool Published { get; set; } + + // de-normalized for perfs + // (when available, copies name from current content version culture variation for the language) + // (otherwise, it's the published one, 'cos we need to have one) + [Column("name")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Name { get; set; } } } diff --git a/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs b/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs index 08bd2a0582..488390f985 100644 --- a/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs @@ -10,33 +10,51 @@ namespace Umbraco.Core.Persistence.Dtos { public const string TableName = Constants.DatabaseSchema.Tables.Language; + /// + /// Gets or sets the identifier of the language. + /// [Column("id")] [PrimaryKeyColumn(IdentitySeed = 2)] public short Id { get; set; } + /// + /// Gets or sets the ISO code of the language. + /// [Column("languageISOCode")] [Index(IndexTypes.UniqueNonClustered)] [NullSetting(NullSetting = NullSettings.Null)] [Length(14)] public string IsoCode { get; set; } + /// + /// Gets or sets the culture name of the language. + /// [Column("languageCultureName")] [NullSetting(NullSetting = NullSettings.Null)] [Length(100)] public string CultureName { get; set; } /// - /// Defines if this language is the default variant language when language variants are in use + /// Gets or sets a value indicating whether the language is the default language. /// [Column("isDefaultVariantLang")] [Constraint(Default = "0")] - public bool IsDefaultVariantLanguage { get; set; } + public bool IsDefault { get; set; } /// - /// If true, a variant node cannot be published unless this language variant is created + /// Gets or sets a value indicating whether the language is mandatory. /// [Column("mandatory")] [Constraint(Default = "0")] - public bool Mandatory { get; set; } + public bool IsMandatory { get; set; } + + /// + /// Gets or sets the identifier of a fallback language. + /// + [Column("fallbackLanguageId")] + [ForeignKey(typeof(LanguageDto), Column = "id")] + [Index(IndexTypes.NonClustered)] + [NullSetting(NullSetting = NullSettings.Null)] + public int? FallbackLanguageId { get; set; } } } diff --git a/src/Umbraco.Core/Persistence/Dtos/NodeDto.cs b/src/Umbraco.Core/Persistence/Dtos/NodeDto.cs index 10450f2bf4..56da821360 100644 --- a/src/Umbraco.Core/Persistence/Dtos/NodeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/NodeDto.cs @@ -45,7 +45,7 @@ namespace Umbraco.Core.Persistence.Dtos [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Trashed")] public bool Trashed { get; set; } - [Column("nodeUser")] // fixme dbfix rename userId + [Column("nodeUser")] // fixme: db rename to 'createUserId' [ForeignKey(typeof(UserDto))] [NullSetting(NullSetting = NullSettings.Null)] public int? UserId { get => _userId == 0 ? null : _userId; set => _userId = value; } //return null if zero @@ -54,10 +54,10 @@ namespace Umbraco.Core.Persistence.Dtos [NullSetting(NullSetting = NullSettings.Null)] public string Text { get; set; } - [Column("nodeObjectType")] // fixme dbfix rename objectType + [Column("nodeObjectType")] // fixme: db rename to 'objectType' [NullSetting(NullSetting = NullSettings.Null)] [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ObjectType")] - public Guid? NodeObjectType { get; set; } // fixme dbfix rename ObjectType + public Guid? NodeObjectType { get; set; } [Column("createDate")] [Constraint(Default = SystemMethods.CurrentDateTime)] diff --git a/src/Umbraco.Core/Persistence/Dtos/TaskDto.cs b/src/Umbraco.Core/Persistence/Dtos/TaskDto.cs deleted file mode 100644 index 9531ffb7b9..0000000000 --- a/src/Umbraco.Core/Persistence/Dtos/TaskDto.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using NPoco; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Persistence.Dtos -{ - [TableName(Constants.DatabaseSchema.Tables.Task)] - [PrimaryKey("id")] - [ExplicitColumns] - internal class TaskDto - { - [Column("closed")] - [Constraint(Default = "0")] - public bool Closed { get; set; } - - [Column("id")] - [PrimaryKeyColumn] - public int Id { get; set; } - - [Column("taskTypeId")] - [ForeignKey(typeof(TaskTypeDto))] - public byte TaskTypeId { get; set; } - - [Column("nodeId")] - [ForeignKey(typeof(NodeDto))] - public int NodeId { get; set; } - - [Column("parentUserId")] - [ForeignKey(typeof(UserDto), Name = "FK_cmsTask_umbracoUser")] - public int ParentUserId { get; set; } - - [Column("userId")] - [ForeignKey(typeof(UserDto), Name = "FK_cmsTask_umbracoUser1")] - public int UserId { get; set; } - - [Column("DateTime")] - [Constraint(Default = SystemMethods.CurrentDateTime)] - public DateTime DateTime { get; set; } - - [Column("Comment")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(500)] - public string Comment { get; set; } - - [ResultColumn] - [Reference(ReferenceType.OneToOne, ColumnName = "TaskTypeId")] - public TaskTypeDto TaskTypeDto { get; set; } - } -} diff --git a/src/Umbraco.Core/Persistence/Dtos/TaskTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/TaskTypeDto.cs deleted file mode 100644 index ca731c29c9..0000000000 --- a/src/Umbraco.Core/Persistence/Dtos/TaskTypeDto.cs +++ /dev/null @@ -1,19 +0,0 @@ -using NPoco; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Persistence.Dtos -{ - [TableName(Constants.DatabaseSchema.Tables.TaskType)] - [PrimaryKey("id")] - [ExplicitColumns] - internal class TaskTypeDto - { - [Column("id")] - [PrimaryKeyColumn(IdentitySeed = 2)] - public byte Id { get; set; } - - [Column("alias")] - [Index(IndexTypes.NonClustered)] - public string Alias { get; set; } - } -} diff --git a/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs b/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs index 7b24411498..ad58c5b570 100644 --- a/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs @@ -8,7 +8,15 @@ namespace Umbraco.Core.Persistence.Factories { public static ILanguage BuildEntity(LanguageDto dto) { - var lang = new Language(dto.IsoCode) { CultureName = dto.CultureName, Id = dto.Id, IsDefaultVariantLanguage = dto.IsDefaultVariantLanguage, Mandatory = dto.Mandatory }; + var lang = new Language(dto.IsoCode) + { + CultureName = dto.CultureName, + Id = dto.Id, + IsDefault = dto.IsDefault, + IsMandatory = dto.IsMandatory, + FallbackLanguageId = dto.FallbackLanguageId + }; + // reset dirty initial properties (U4-1946) lang.ResetDirtyProperties(false); return lang; @@ -16,9 +24,19 @@ namespace Umbraco.Core.Persistence.Factories public static LanguageDto BuildDto(ILanguage entity) { - var dto = new LanguageDto { CultureName = entity.CultureName, IsoCode = entity.IsoCode, IsDefaultVariantLanguage = entity.IsDefaultVariantLanguage, Mandatory = entity.Mandatory }; + var dto = new LanguageDto + { + CultureName = entity.CultureName, + IsoCode = entity.IsoCode, + IsDefault = entity.IsDefault, + IsMandatory = entity.IsMandatory, + FallbackLanguageId = entity.FallbackLanguageId + }; + if (entity.HasIdentity) + { dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture)); + } return dto; } diff --git a/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs b/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs deleted file mode 100644 index 49bcbd4262..0000000000 --- a/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Factories -{ - /// - /// Creates the model mappings for Tasks - /// - internal static class TaskFactory - { - public static Task BuildEntity(TaskDto dto) - { - var entity = new Task(new TaskType(dto.TaskTypeDto.Alias) { Id = dto.TaskTypeDto.Id }); - - try - { - entity.DisableChangeTracking(); - - entity.Closed = dto.Closed; - entity.AssigneeUserId = dto.UserId; - entity.Comment = dto.Comment; - entity.CreateDate = dto.DateTime; - entity.EntityId = dto.NodeId; - entity.Id = dto.Id; - entity.OwnerUserId = dto.ParentUserId; - - // reset dirty initial properties (U4-1946) - entity.ResetDirtyProperties(false); - return entity; - } - finally - { - entity.EnableChangeTracking(); - } - } - - public static TaskDto BuildDto(Task entity) - { - var dto = new TaskDto - { - Closed = entity.Closed, - Comment = entity.Comment, - DateTime = entity.CreateDate, - Id = entity.Id, - NodeId = entity.EntityId, - ParentUserId = entity.OwnerUserId, - TaskTypeId = (byte)entity.TaskType.Id, - UserId = entity.AssigneeUserId - }; - - return dto; - } - } -} diff --git a/src/Umbraco.Core/Persistence/Factories/TaskTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/TaskTypeFactory.cs deleted file mode 100644 index 01a25ff852..0000000000 --- a/src/Umbraco.Core/Persistence/Factories/TaskTypeFactory.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Factories -{ - internal static class TaskTypeFactory - { - public static TaskType BuildEntity(TaskTypeDto dto) - { - var entity = new TaskType(dto.Alias) {Id = dto.Id}; - // reset dirty initial properties (U4-1946) - entity.ResetDirtyProperties(false); - return entity; - } - - public static TaskTypeDto BuildDto(TaskType entity) - { - var dto = new TaskTypeDto - { - Id = (byte)entity.Id, - Alias = entity.Alias - }; - return dto; - } - } -} diff --git a/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs b/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs index f861c450aa..65bfbcdbaf 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs @@ -47,7 +47,6 @@ namespace Umbraco.Core.Persistence.Mappers Add(); Add(); Add(); - Add(); Add(); Add(); Add(); diff --git a/src/Umbraco.Core/Persistence/Mappers/TaskTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/TaskTypeMapper.cs deleted file mode 100644 index f503fc1684..0000000000 --- a/src/Umbraco.Core/Persistence/Mappers/TaskTypeMapper.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(TaskType))] - public sealed class TaskTypeMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Alias, dto => dto.Alias); - } - } -} diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs index e40e6dedd3..bd44c095fa 100644 --- a/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs @@ -22,6 +22,11 @@ namespace Umbraco.Core.Persistence return databaseType is NPoco.DatabaseTypes.SqlServer2008DatabaseType; } + public static bool IsSqlServer2012OrLater(this DatabaseType databaseType) + { + return databaseType is NPoco.DatabaseTypes.SqlServer2012DatabaseType; + } + public static bool IsSqlCe(this DatabaseType databaseType) { return databaseType is NPoco.DatabaseTypes.SqlServerCEDatabaseType; diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs index 9c1f0d9a07..d97c748b6f 100644 --- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs @@ -7,7 +7,6 @@ using System.Reflection; using System.Text; using NPoco; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence { @@ -74,12 +73,10 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql Where(this Sql sql, Expression> predicate, string alias = null) { - var expresionist = new PocoToSqlExpressionVisitor(sql.SqlContext, alias); - var whereExpression = expresionist.Visit(predicate); - sql.Where(whereExpression, expresionist.GetSqlParameters()); - return sql; + var (s, a) = sql.SqlContext.Visit(predicate, alias); + return sql.Where(s, a); } - + /// /// Appends a WHERE clause to the Sql statement. /// @@ -92,10 +89,8 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql Where(this Sql sql, Expression> predicate, string alias1 = null, string alias2 = null) { - var expresionist = new PocoToSqlExpressionVisitor(sql.SqlContext, alias1, alias2); - var whereExpression = expresionist.Visit(predicate); - sql.Where(whereExpression, expresionist.GetSqlParameters()); - return sql; + var (s, a) = sql.SqlContext.Visit(predicate, alias1, alias2); + return sql.Where(s, a); } /// @@ -108,7 +103,7 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql WhereIn(this Sql sql, Expression> field, IEnumerable values) { - var fieldName = GetFieldName(field, sql.SqlContext.SqlSyntax); + var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(field); sql.Where(fieldName + " IN (@values)", new { values }); return sql; } @@ -136,7 +131,7 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql WhereNotIn(this Sql sql, Expression> field, IEnumerable values) { - var fieldName = GetFieldName(field, sql.SqlContext.SqlSyntax); + var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(field); sql.Where(fieldName + " NOT IN (@values)", new { values }); return sql; } @@ -164,7 +159,8 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql WhereAnyIn(this Sql sql, Expression>[] fields, IEnumerable values) { - var fieldNames = fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray(); + var sqlSyntax = sql.SqlContext.SqlSyntax; + var fieldNames = fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray(); var sb = new StringBuilder(); sb.Append("("); for (var i = 0; i < fieldNames.Length; i++) @@ -180,7 +176,7 @@ namespace Umbraco.Core.Persistence private static Sql WhereIn(this Sql sql, Expression> fieldSelector, Sql valuesSql, bool not) { - var fieldName = GetFieldName(fieldSelector, sql.SqlContext.SqlSyntax); + var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(fieldSelector); sql.Where(fieldName + (not ? " NOT" : "") +" IN (" + valuesSql.SQL + ")", valuesSql.Arguments); return sql; } @@ -274,7 +270,7 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql OrderBy(this Sql sql, Expression> field) { - return sql.OrderBy("(" + GetFieldName(field, sql.SqlContext.SqlSyntax) + ")"); + return sql.OrderBy("(" + sql.SqlContext.SqlSyntax.GetFieldName(field) + ")"); } /// @@ -286,9 +282,10 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql OrderBy(this Sql sql, params Expression>[] fields) { + var sqlSyntax = sql.SqlContext.SqlSyntax; var columns = fields.Length == 0 ? sql.GetColumns(withAlias: false) - : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray(); + : fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray(); return sql.OrderBy(columns); } @@ -301,7 +298,7 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql OrderByDescending(this Sql sql, Expression> field) { - return sql.OrderBy("(" + GetFieldName(field, sql.SqlContext.SqlSyntax) + ") DESC"); + return sql.OrderBy("(" + sql.SqlContext.SqlSyntax.GetFieldName(field) + ") DESC"); } /// @@ -313,9 +310,10 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql OrderByDescending(this Sql sql, params Expression>[] fields) { + var sqlSyntax = sql.SqlContext.SqlSyntax; var columns = fields.Length == 0 ? sql.GetColumns(withAlias: false) - : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray(); + : fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray(); return sql.OrderBy(columns.Select(x => x + " DESC")); } @@ -339,7 +337,7 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql GroupBy(this Sql sql, Expression> field) { - return sql.GroupBy(GetFieldName(field, sql.SqlContext.SqlSyntax)); + return sql.GroupBy(sql.SqlContext.SqlSyntax.GetFieldName(field)); } /// @@ -351,9 +349,10 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql GroupBy(this Sql sql, params Expression>[] fields) { + var sqlSyntax = sql.SqlContext.SqlSyntax; var columns = fields.Length == 0 ? sql.GetColumns(withAlias: false) - : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray(); + : fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray(); return sql.GroupBy(columns); } @@ -366,9 +365,10 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql AndBy(this Sql sql, params Expression>[] fields) { + var sqlSyntax = sql.SqlContext.SqlSyntax; var columns = fields.Length == 0 ? sql.GetColumns(withAlias: false) - : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray(); + : fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray(); return sql.Append(", " + string.Join(", ", columns)); } @@ -381,9 +381,10 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql AndByDescending(this Sql sql, params Expression>[] fields) { + var sqlSyntax = sql.SqlContext.SqlSyntax; var columns = fields.Length == 0 ? sql.GetColumns(withAlias: false) - : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray(); + : fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray(); return sql.Append(", " + string.Join(", ", columns.Select(x => x + " DESC"))); } @@ -391,6 +392,23 @@ namespace Umbraco.Core.Persistence #region Joins + /// + /// Appends a CROSS JOIN clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// An optional alias for the joined table. + /// The Sql statement. + public static Sql CrossJoin(this Sql sql, string alias = null) + { + var type = typeof(TDto); + var tableName = type.GetTableName(); + var join = sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName); + if (alias != null) join += " " + sql.SqlContext.SqlSyntax.GetQuotedTableName(alias); + + return sql.Append("CROSS JOIN " + join); + } + /// /// Appends an INNER JOIN clause to the Sql statement. /// @@ -532,6 +550,25 @@ namespace Umbraco.Core.Persistence return sqlJoin.On(onExpression, expresionist.GetSqlParameters()); } + /// + /// Appends an ON clause to a SqlJoin statement. + /// + /// The type of Dto 1. + /// The type of Dto 2. + /// The type of Dto 3. + /// The SqlJoin statement. + /// A predicate to transform and use as the ON clause body. + /// An optional alias for Dto 1 table. + /// An optional alias for Dto 2 table. + /// An optional alias for Dto 3 table. + /// The Sql statement. + public static Sql On(this Sql.SqlJoinClause sqlJoin, Expression> predicate, string aliasLeft = null, string aliasRight = null, string aliasOther = null) + { + var expresionist = new PocoToSqlExpressionVisitor(sqlJoin.SqlContext, aliasLeft, aliasRight, aliasOther); + var onExpression = expresionist.Visit(predicate); + return sqlJoin.On(onExpression, expresionist.GetSqlParameters()); + } + #endregion #region Select @@ -572,9 +609,10 @@ namespace Umbraco.Core.Persistence public static Sql SelectCount(this Sql sql, params Expression>[] fields) { if (sql == null) throw new ArgumentNullException(nameof(sql)); + var sqlSyntax = sql.SqlContext.SqlSyntax; var columns = fields.Length == 0 ? sql.GetColumns(withAlias: false) - : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray(); + : fields.Select(x => sqlSyntax.GetFieldName(x)).ToArray(); return sql.Select("COUNT (" + string.Join(", ", columns) + ")"); } @@ -906,7 +944,7 @@ namespace Umbraco.Core.Persistence public SqlUpd Set(Expression> fieldSelector, object value) { - var fieldName = GetFieldName(fieldSelector, _sqlContext.SqlSyntax); + var fieldName = _sqlContext.SqlSyntax.GetFieldName(fieldSelector); _setExpressions.Add(new Tuple(fieldName, value)); return this; } @@ -1062,17 +1100,6 @@ namespace Umbraco.Core.Persistence return string.IsNullOrWhiteSpace(attr?.Name) ? column.Name : attr.Name; } - private static string GetFieldName(Expression> fieldSelector, ISqlSyntaxProvider sqlSyntax) - { - var field = ExpressionHelper.FindProperty(fieldSelector).Item1 as PropertyInfo; - var fieldName = field.GetColumnName(); - - var type = typeof (TDto); - var tableName = type.GetTableName(); - - return sqlSyntax.GetQuotedTableName(tableName) + "." + sqlSyntax.GetQuotedColumnName(fieldName); - } - internal static void WriteToConsole(this Sql sql) { Console.WriteLine(sql.SQL); diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs index 4d33977c72..971b65c220 100644 --- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs +++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs @@ -167,4 +167,93 @@ namespace Umbraco.Core.Persistence.Querying } } + /// + /// Represents an expression tree parser used to turn strongly typed expressions into SQL statements. + /// + /// The type of DTO 1. + /// The type of DTO 2. + /// The type of DTO 3. + /// This visitor is stateful and cannot be reused. + internal class PocoToSqlExpressionVisitor : ExpressionVisitorBase + { + private readonly PocoData _pocoData1, _pocoData2, _pocoData3; + private readonly string _alias1, _alias2, _alias3; + private string _parameterName1, _parameterName2, _parameterName3; + + public PocoToSqlExpressionVisitor(ISqlContext sqlContext, string alias1, string alias2, string alias3) + : base(sqlContext.SqlSyntax) + { + _pocoData1 = sqlContext.PocoDataFactory.ForType(typeof(TDto1)); + _pocoData2 = sqlContext.PocoDataFactory.ForType(typeof(TDto2)); + _pocoData3 = sqlContext.PocoDataFactory.ForType(typeof(TDto3)); + _alias1 = alias1; + _alias2 = alias2; + _alias3 = alias3; + } + + protected override string VisitLambda(LambdaExpression lambda) + { + if (lambda.Parameters.Count == 3) + { + _parameterName1 = lambda.Parameters[0].Name; + _parameterName2 = lambda.Parameters[1].Name; + _parameterName3 = lambda.Parameters[2].Name; + } + else if (lambda.Parameters.Count == 2) + { + _parameterName1 = lambda.Parameters[0].Name; + _parameterName2 = lambda.Parameters[1].Name; + } + else + { + _parameterName1 = _parameterName2 = null; + } + return base.VisitLambda(lambda); + } + + protected override string VisitMemberAccess(MemberExpression m) + { + if (m.Expression != null) + { + if (m.Expression.NodeType == ExpressionType.Parameter) + { + var pex = (ParameterExpression)m.Expression; + + if (pex.Name == _parameterName1) + return Visited ? string.Empty : GetFieldName(_pocoData1, m.Member.Name, _alias1); + + if (pex.Name == _parameterName2) + return Visited ? string.Empty : GetFieldName(_pocoData2, m.Member.Name, _alias2); + + if (pex.Name == _parameterName3) + return Visited ? string.Empty : GetFieldName(_pocoData3, m.Member.Name, _alias3); + } + else if (m.Expression.NodeType == ExpressionType.Convert) + { + // here: which _pd should we use?! + throw new NotSupportedException(); + //return Visited ? string.Empty : GetFieldName(_pd, m.Member.Name); + } + } + + var member = Expression.Convert(m, typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + var o = getter(); + + SqlParameters.Add(o); + + // execute if not already compiled + return Visited ? string.Empty : "@" + (SqlParameters.Count - 1); + } + + protected virtual string GetFieldName(PocoData pocoData, string name, string alias) + { + var column = pocoData.Columns.FirstOrDefault(x => x.Value.MemberInfoData.Name == name); + var tableName = SqlSyntax.GetQuotedTableName(alias ?? pocoData.TableInfo.TableName); + var columnName = SqlSyntax.GetQuotedColumnName(column.Value.ColumnName); + + return tableName + "." + columnName; + } + } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs index fea0a61589..f7341d112b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { @@ -67,7 +68,8 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Gets paged content items. /// + /// Here, can be null but cannot. IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null); + IQuery filter, Ordering ordering); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/ITaskRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITaskRepository.cs deleted file mode 100644 index 10c517fa72..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/ITaskRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Persistence.Repositories -{ - public interface ITaskRepository : IReadWriteQueryRepository - { - IEnumerable GetTasks(int? itemId = null, int? assignedUser = null, int? ownerUser = null, string taskTypeAlias = null, bool includeClosed = false); - } -} diff --git a/src/Umbraco.Core/Persistence/Repositories/ITaskTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITaskTypeRepository.cs deleted file mode 100644 index 9fe483b462..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/ITaskTypeRepository.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Umbraco.Core.Models; - -namespace Umbraco.Core.Persistence.Repositories -{ - public interface ITaskTypeRepository : IReadWriteQueryRepository - { } -} diff --git a/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs index 83d4148689..4bab445bee 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs @@ -18,25 +18,6 @@ namespace Umbraco.Core.Persistence.Repositories IEnumerable GetDescendants(int masterTemplateId); IEnumerable GetDescendants(string alias); - /// - /// Returns a template as a template node which can be traversed (parent, children) - /// - /// - /// - [Obsolete("Use GetDescendants instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - TemplateNode GetTemplateNode(string alias); - - /// - /// Given a template node in a tree, this will find the template node with the given alias if it is found in the hierarchy, otherwise null - /// - /// - /// - /// - [Obsolete("Use GetDescendants instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - TemplateNode FindTemplateInTree(TemplateNode anyNode, string alias); - /// /// This checks what the default rendering engine is set in config but then also ensures that there isn't already /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 2e0139aa30..6ace73fbc3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -232,16 +232,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion - public abstract IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null); - - // sql: the main sql - // filterSql: a filtering ? fixme different from v7? - // orderBy: the name of an ordering field - // orderDirection: direction for orderBy - // orderBySystemField: whether orderBy is a system field or a custom field (property value) - private Sql PrepareSqlForPage(Sql sql, Sql filterSql, string orderBy, Direction orderDirection, bool orderBySystemField) + private Sql PreparePageSql(Sql sql, Sql filterSql, Ordering ordering) { - if (filterSql == null && string.IsNullOrEmpty(orderBy)) return sql; + // non-filtering, non-ordering = nothing to do + if (filterSql == null && ordering.IsEmpty) return sql; // preserve original var psql = new Sql(sql.SqlContext, sql.SQL, sql.Arguments); @@ -251,74 +245,131 @@ namespace Umbraco.Core.Persistence.Repositories.Implement psql.Append(filterSql); // non-sorting, we're done - if (string.IsNullOrEmpty(orderBy)) + if (ordering.IsEmpty) return psql; - // else apply sort - var dbfield = orderBySystemField - ? GetOrderBySystemField(ref psql, orderBy) - : GetOrderByNonSystemField(ref psql, orderBy); - - if (orderDirection == Direction.Ascending) - psql.OrderBy(dbfield); - else - psql.OrderByDescending(dbfield); + // else apply ordering + ApplyOrdering(ref psql, ordering); // no matter what we always MUST order the result also by umbracoNode.id to ensure that all records being ordered by are unique. // if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column // is empty for many nodes) - see: http://issues.umbraco.org/issue/U4-8831 - dbfield = GetDatabaseFieldNameForOrderBy("umbracoNode", "id"); - if (orderBySystemField == false || orderBy.InvariantEquals(dbfield) == false) + var dbfield = GetQuotedFieldName("umbracoNode", "id"); + (dbfield, _) = SqlContext.Visit(x => x.NodeId); // fixme?! + if (ordering.IsCustomField || !ordering.OrderBy.InvariantEquals("id")) { - // get alias, if aliased - var matches = SqlContext.SqlSyntax.AliasRegex.Matches(sql.SQL); - var match = matches.Cast().FirstOrDefault(m => m.Groups[1].Value.InvariantEquals(dbfield)); - if (match != null) dbfield = match.Groups[2].Value; - - // add field - psql.OrderBy(dbfield); + psql.OrderBy(GetAliasedField(dbfield, sql)); // fixme why aliased? } // create prepared sql // ensure it's single-line as NPoco PagingHelper has issues with multi-lines - psql = new Sql(psql.SqlContext, psql.SQL.ToSingleLine(), psql.Arguments); + psql = Sql(psql.SQL.ToSingleLine(), psql.Arguments); return psql; } - private string GetOrderBySystemField(ref Sql sql, string orderBy) + private void ApplyOrdering(ref Sql sql, Ordering ordering) { - // get the database field eg "[table].[column]" - var dbfield = GetDatabaseFieldNameForOrderBy(orderBy); + if (sql == null) throw new ArgumentNullException(nameof(sql)); + if (ordering == null) throw new ArgumentNullException(nameof(ordering)); - // for SqlServer pagination to work, the "order by" field needs to be the alias eg if - // the select statement has "umbracoNode.text AS NodeDto__Text" then the order field needs - // to be "NodeDto__Text" and NOT "umbracoNode.text". - // not sure about SqlCE nor MySql, so better do it too. initially thought about patching - // NPoco but that would be expensive and not 100% possible, so better give NPoco proper - // queries to begin with. - // thought about maintaining a map of columns-to-aliases in the sql context but that would - // be expensive and most of the time, useless. so instead we parse the SQL looking for the - // alias. somewhat expensive too but nothing's free. + var orderBy = ordering.IsCustomField + ? ApplyCustomOrdering(ref sql, ordering) + : ApplySystemOrdering(ref sql, ordering); - // note: ContentTypeAlias is not properly managed because it's not part of the query to begin with! + // beware! NPoco paging code parses the query to isolate the ORDER BY fragment, + // using a regex that wants "([\w\.\[\]\(\)\s""`,]+)" - meaning that anything + // else in orderBy is going to break NPoco / not be detected - // get alias, if aliased - var matches = SqlContext.SqlSyntax.AliasRegex.Matches(sql.SQL); - var match = matches.Cast().FirstOrDefault(m => m.Groups[1].Value.InvariantEquals(dbfield)); - if (match != null) dbfield = match.Groups[2].Value; + // beware! NPoco paging code (in PagingHelper) collapses everything [foo].[bar] + // to [bar] only, so we MUST use aliases, cannot use [table].[field] - return dbfield; + // beware! pre-2012 SqlServer is using a convoluted syntax for paging, which + // includes "SELECT ROW_NUMBER() OVER (ORDER BY ...) poco_rn FROM SELECT (...", + // so anything added here MUST also be part of the inner SELECT statement, ie + // the original statement, AND must be using the proper alias, as the inner SELECT + // will hide the original table.field names entirely + + if (ordering.Direction == Direction.Ascending) + sql.OrderBy(orderBy); + else + sql.OrderByDescending(orderBy); } - private string GetOrderByNonSystemField(ref Sql sql, string orderBy) + protected virtual string ApplySystemOrdering(ref Sql sql, Ordering ordering) + { + // id is invariant + if (ordering.OrderBy.InvariantEquals("id")) + return GetAliasedField(SqlSyntax.GetFieldName(x => x.NodeId), sql); + + // sort order is invariant + if (ordering.OrderBy.InvariantEquals("sortOrder")) + return GetAliasedField(SqlSyntax.GetFieldName(x => x.SortOrder), sql); + + // path is invariant + if (ordering.OrderBy.InvariantEquals("path")) + return GetAliasedField(SqlSyntax.GetFieldName(x => x.Path), sql); + + // note: 'owner' is the user who created the item as a whole, + // we don't have an 'owner' per culture (should we?) + if (ordering.OrderBy.InvariantEquals("owner")) + { + var joins = Sql() + .InnerJoin("ownerUser").On((node, user) => node.UserId == user.Id, aliasRight: "ownerUser"); + + // see notes in ApplyOrdering: the field MUST be selected + aliased + sql = Sql(InsertBefore(sql, "FROM", ", " + SqlSyntax.GetFieldName(x => x.UserName, "ownerUser") + " AS ordering "), sql.Arguments); + + sql = InsertJoins(sql, joins); + + return "ordering"; + } + + // note: each version culture variation has a date too, + // maybe we would want to use it instead? + if (ordering.OrderBy.InvariantEquals("versionDate") || ordering.OrderBy.InvariantEquals("updateDate")) + return GetAliasedField(SqlSyntax.GetFieldName(x => x.VersionDate), sql); + + // create date is invariant (we don't keep each culture's creation date) + if (ordering.OrderBy.InvariantEquals("createDate")) + return GetAliasedField(SqlSyntax.GetFieldName(x => x.CreateDate), sql); + + // name is variant + if (ordering.OrderBy.InvariantEquals("name")) + { + // no culture = can only work on the invariant name + // see notes in ApplyOrdering: the field MUST be aliased + if (ordering.Culture.IsNullOrWhiteSpace()) + return GetAliasedField(SqlSyntax.GetFieldName(x => x.Text), sql); + + // culture = must work on variant name ?? invariant name + // insert proper join and return coalesced ordering field + + var joins = Sql() + .LeftJoin(nested => + nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == ordering.Culture, "ccv", "lang"), "ccv") + .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); + + // see notes in ApplyOrdering: the field MUST be selected + aliased + sql = Sql(InsertBefore(sql, "FROM", ", " + SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql + " AS ordering "), sql.Arguments); + + sql = InsertJoins(sql, joins); + + return "ordering"; + } + + // previously, we'd accept anything and just sanitize it - not anymore + throw new NotSupportedException($"Ordering by {ordering.OrderBy} not supported."); + } + + private string ApplyCustomOrdering(ref Sql sql, Ordering ordering) { // sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through value // from 'current' content version for the given order by field var sortedInt = string.Format(SqlContext.SqlSyntax.ConvertIntegerToOrderableString, "intValue"); + var sortedDecimal = string.Format(SqlContext.SqlSyntax.ConvertDecimalToOrderableString, "decimalValue"); var sortedDate = string.Format(SqlContext.SqlSyntax.ConvertDateToOrderableString, "dateValue"); var sortedString = "COALESCE(varcharValue,'')"; // assuming COALESCE is ok for all syntaxes - var sortedDecimal = string.Format(SqlContext.SqlSyntax.ConvertDecimalToOrderableString, "decimalValue"); // needs to be an outer join since there's no guarantee that any of the nodes have values for this property var innerSql = Sql().Select($@"CASE @@ -329,49 +380,60 @@ namespace Umbraco.Core.Persistence.Repositories.Implement END AS customPropVal, cver.nodeId AS customPropNodeId") .From("cver") - .InnerJoin("opdata").On((left, right) => left.Id == right.Id, "cver", "opdata") - .InnerJoin("optype").On((left, right) => left.PropertyTypeId == right.Id, "opdata", "optype") + .InnerJoin("opdata") + .On((version, pdata) => version.Id == pdata.VersionId, "cver", "opdata") + .InnerJoin("optype").On((pdata, ptype) => pdata.PropertyTypeId == ptype.Id, "opdata", "optype") + .LeftJoin().On((pdata, lang) => pdata.LanguageId == lang.Id, "opdata") .Where(x => x.Current, "cver") // always query on current (edit) values - .Where(x => x.Alias == "", "optype"); + .Where(x => x.Alias == ordering.OrderBy, "optype") + .Where((opdata, lang) => opdata.LanguageId == null || lang.IsoCode == ordering.Culture, "opdata"); - // @0 is for x.Current ie 'true' = 1 - // @1 is for x.Alias - var innerSqlString = innerSql.SQL.Replace("@0", "1").Replace("@1", "@" + sql.Arguments.Length); + // merge arguments + var argsList = sql.Arguments.ToList(); + var innerSqlString = ParameterHelper.ProcessParams(innerSql.SQL, innerSql.Arguments, argsList); + + // create the outer join complete sql fragment var outerJoinTempTable = $@"LEFT OUTER JOIN ({innerSqlString}) AS customPropData ON customPropData.customPropNodeId = {Constants.DatabaseSchema.Tables.Node}.id "; // trailing space is important! - // insert this just above the last WHERE - var pos = sql.SQL.InvariantIndexOf("WHERE"); - if (pos < 0) throw new Exception("Oops, WHERE not found."); - var newSql = sql.SQL.Insert(pos, outerJoinTempTable); + // insert this just above the first WHERE + var newSql = InsertBefore(sql.SQL, "WHERE", outerJoinTempTable); - var newArgs = sql.Arguments.ToList(); - newArgs.Add(orderBy); + // see notes in ApplyOrdering: the field MUST be selected + aliased + newSql = InsertBefore(newSql, "FROM", ", customPropData.customPropVal AS ordering "); // trailing space is important! - // insert the SQL selected field, too, else ordering cannot work - if (sql.SQL.StartsWith("SELECT ") == false) throw new Exception("Oops: SELECT not found."); - newSql = newSql.Insert("SELECT ".Length, "customPropData.customPropVal, "); - - sql = new Sql(sql.SqlContext, newSql, newArgs.ToArray()); + // create the new sql + sql = Sql(newSql, argsList.ToArray()); // and order by the custom field - return "customPropData.customPropVal"; + // this original code means that an ascending sort would first expose all NULL values, ie items without a value + return "ordering"; + + // note: adding an extra sorting criteria on + // "(CASE WHEN customPropData.customPropVal IS NULL THEN 1 ELSE 0 END") + // would ensure that items without a value always come last, both in ASC and DESC-ending sorts } + public abstract IEnumerable GetPage(IQuery query, + long pageIndex, int pageSize, out long totalRecords, + IQuery filter, + Ordering ordering); + + // here, filter can be null and ordering cannot protected IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords, Func, IEnumerable> mapDtos, - string orderBy, Direction orderDirection, bool orderBySystemField, - Sql filterSql = null) // fixme filter is different on v7? + Sql filter, + Ordering ordering) { - if (orderBy == null) throw new ArgumentNullException(nameof(orderBy)); + if (ordering == null) throw new ArgumentNullException(nameof(ordering)); // start with base query, and apply the supplied IQuery - if (query == null) query = AmbientScope.SqlContext.Query(); + if (query == null) query = Query(); var sql = new SqlTranslator(GetBaseQuery(QueryType.Many), query).Translate(); // sort and filter - sql = PrepareSqlForPage(sql, filterSql, orderBy, orderDirection, orderBySystemField); + sql = PreparePageSql(sql, filter, ordering); // get a page of DTOs and the total count var pagedResult = Database.Page(pageIndex + 1, pageSize, sql); @@ -498,36 +560,48 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return result; } - protected virtual string GetDatabaseFieldNameForOrderBy(string orderBy) - { - // translate the supplied "order by" field, which were originally defined for in-memory - // object sorting of ContentItemBasic instance, to the actual database field names. + protected string InsertBefore(Sql s, string atToken, string insert) + => InsertBefore(s.SQL, atToken, insert); - switch (orderBy.ToUpperInvariant()) - { - case "VERSIONDATE": - case "UPDATEDATE": - return GetDatabaseFieldNameForOrderBy(Constants.DatabaseSchema.Tables.ContentVersion, "versionDate"); - case "CREATEDATE": - return GetDatabaseFieldNameForOrderBy("umbracoNode", "createDate"); - case "NAME": - return GetDatabaseFieldNameForOrderBy("umbracoNode", "text"); - case "PUBLISHED": - return GetDatabaseFieldNameForOrderBy(Constants.DatabaseSchema.Tables.Document, "published"); - case "OWNER": - //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return GetDatabaseFieldNameForOrderBy("umbracoNode", "nodeUser"); - case "PATH": - return GetDatabaseFieldNameForOrderBy("umbracoNode", "path"); - case "SORTORDER": - return GetDatabaseFieldNameForOrderBy("umbracoNode", "sortOrder"); - default: - //ensure invalid SQL cannot be submitted - return Regex.Replace(orderBy, @"[^\w\.,`\[\]@-]", ""); - } + protected string InsertBefore(string s, string atToken, string insert) + { + var pos = s.InvariantIndexOf(atToken); + if (pos < 0) throw new Exception($"Could not find token \"{atToken}\"."); + return s.Insert(pos, insert); } - protected string GetDatabaseFieldNameForOrderBy(string tableName, string fieldName) + protected Sql InsertJoins(Sql sql, Sql joins) + { + var joinsSql = joins.SQL; + var args = sql.Arguments; + + // merge args if any + if (joins.Arguments.Length > 0) + { + var argsList = args.ToList(); + joinsSql = ParameterHelper.ProcessParams(joinsSql, joins.Arguments, argsList); + args = argsList.ToArray(); + } + + return Sql(InsertBefore(sql.SQL, "WHERE", joinsSql), args); + } + + private string GetAliasedField(string field, Sql sql) + { + // get alias, if aliased + // + // regex looks for pattern "([\w+].[\w+]) AS ([\w+])" ie "(field) AS (alias)" + // and, if found & a group's field matches the field name, returns the alias + // + // so... if query contains "[umbracoNode].[nodeId] AS [umbracoNode__nodeId]" + // then GetAliased for "[umbracoNode].[nodeId]" returns "[umbracoNode__nodeId]" + + var matches = SqlContext.SqlSyntax.AliasRegex.Matches(sql.SQL); + var match = matches.Cast().FirstOrDefault(m => m.Groups[1].Value.InvariantEquals(field)); + return match == null ? field : match.Groups[2].Value; + } + + protected string GetQuotedFieldName(string tableName, string fieldName) { return SqlContext.SqlSyntax.GetQuotedTableName(tableName) + "." + SqlContext.SqlSyntax.GetQuotedColumnName(fieldName); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 28c0b0dec6..bf41cd1ad1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -8,13 +8,12 @@ using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; -using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -168,7 +167,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var list = new List { "DELETE FROM " + Constants.DatabaseSchema.Tables.RedirectUrl + " WHERE contentKey IN (SELECT uniqueId FROM " + Constants.DatabaseSchema.Tables.Node + " WHERE id = @id)", - "DELETE FROM " + Constants.DatabaseSchema.Tables.Task + " WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.User2NodeNotify + " WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.UserGroup2NodePermission + " WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.UserStartNode + " WHERE startNode = @id", @@ -504,8 +502,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // names also impact 'edited' foreach (var (culture, name) in content.CultureNames) if (name != content.GetPublishName(culture)) + { + edited = true; (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(culture); + // fixme - change tracking + // at the moment, we don't do any dirty tracking on property values, so we don't know whether the + // culture has just been edited or not, so we don't update its update date - that date only changes + // when the name is set, and it all works because the controller does it - but, if someone uses a + // service to change a property value and save (without setting name), the update date does not change. + } + // replace the content version variations (rather than updating) // only need to delete for the version that existed, the new version (if any) has no property data yet var deleteContentVariations = Sql().Delete().Where(x => x.VersionId == versionToDelete); @@ -673,13 +680,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement PermissionRepository.Save(permission); } - /// - /// Gets paged content results. - /// + /// public override IEnumerable GetPage(IQuery query, - long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, - IQuery filter = null) + long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering) { Sql filterSql = null; @@ -692,8 +696,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return GetPage(query, pageIndex, pageSize, out totalRecords, x => MapDtosToContent(x), - orderBy, orderDirection, orderBySystemField, - filterSql); + filterSql, + ordering); } public bool IsPathPublished(IContent content) @@ -814,25 +818,59 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion - protected override string GetDatabaseFieldNameForOrderBy(string orderBy) + protected override string ApplySystemOrdering(ref Sql sql, Ordering ordering) { - // NOTE see sortby.prevalues.controller.js for possible values - // that need to be handled here or in VersionableRepositoryBase - - //Some custom ones - switch (orderBy.ToUpperInvariant()) + // note: 'updater' is the user who created the latest draft version, + // we don't have an 'updater' per culture (should we?) + if (ordering.OrderBy.InvariantEquals("updater")) { - case "UPDATER": - // fixme orders by id not letter = bad - return GetDatabaseFieldNameForOrderBy(Constants.DatabaseSchema.Tables.ContentVersion, "userId"); - case "PUBLISHED": - // fixme kill - return GetDatabaseFieldNameForOrderBy(Constants.DatabaseSchema.Tables.Document, "published"); - case "CONTENTTYPEALIAS": - throw new NotSupportedException("Don't know how to support ContentTypeAlias."); + var joins = Sql() + .InnerJoin("updaterUser").On((version, user) => version.UserId == user.Id, aliasRight: "updaterUser"); + + // see notes in ApplyOrdering: the field MUST be selected + aliased + sql = Sql(InsertBefore(sql, "FROM", SqlSyntax.GetFieldName(x => x.UserName, "updaterUser") + " AS ordering"), sql.Arguments); + + sql = InsertJoins(sql, joins); + + return "ordering"; } - return base.GetDatabaseFieldNameForOrderBy(orderBy); + if (ordering.OrderBy.InvariantEquals("published")) + { + // no culture = can only work on the global 'published' flag + if (ordering.Culture.IsNullOrWhiteSpace()) + { + // see notes in ApplyOrdering: the field MUST be selected + aliased, and we cannot have + // the whole CASE fragment in ORDER BY due to it not being detected by NPoco + sql = Sql(InsertBefore(sql, "FROM", ", (CASE WHEN pcv.id IS NULL THEN 0 ELSE 1 END) AS ordering "), sql.Arguments); + return "ordering"; + } + + // invariant: left join will yield NULL and we must use pcv to determine published + // variant: left join may yield NULL or something, and that determines published + + var joins = Sql() + .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype") + .LeftJoin(nested => + nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == ordering.Culture, "ccv", "lang"), "ccv") + .On((pcv, ccv) => pcv.Id == ccv.VersionId, "pcv", "ccv"); // join on *published* content version + + sql = InsertJoins(sql, joins); + + // see notes in ApplyOrdering: the field MUST be selected + aliased, and we cannot have + // the whole CASE fragment in ORDER BY due to it not being detected by NPoco + var sqlText = InsertBefore(sql.SQL, "FROM", + + // when invariant, ie 'variations' does not have the culture flag (value 1), use the global 'published' flag on pcv.id, + // otherwise check if there's a version culture variation for the lang, via ccv.id + ", (CASE WHEN (ctype.variations & 1) = 0 THEN (CASE WHEN pcv.id IS NULL THEN 0 ELSE 1 END) ELSE (CASE WHEN ccv.id IS NULL THEN 0 ELSE 1 END) END) AS ordering "); // trailing space is important! + + sql = Sql(sqlText, sql.Arguments); + + return "ordering"; + } + + return base.ApplySystemOrdering(ref sql, ordering); } private IEnumerable MapDtosToContent(List dtos, bool withCache = false) @@ -1001,7 +1039,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { Culture = LanguageRepository.GetIsoCodeById(dto.LanguageId), Name = dto.Name, - Date = dto.Date + Date = dto.UpdateDate }); } @@ -1046,7 +1084,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."), Culture = culture, Name = name, - Date = content.GetCultureDate(culture) ?? DateTime.MinValue // we *know* there is a value + UpdateDate = content.GetUpdateDate(culture) ?? DateTime.MinValue // we *know* there is a value }; // if not publishing, we're just updating the 'current' (non-published) version, @@ -1061,22 +1099,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."), Culture = culture, Name = name, - Date = content.GetPublishDate(culture) ?? DateTime.MinValue // we *know* there is a value + UpdateDate = content.GetPublishDate(culture) ?? DateTime.MinValue // we *know* there is a value }; } private IEnumerable GetDocumentVariationDtos(IContent content, bool publishing, HashSet editedCultures) { - foreach (var (culture, name) in content.CultureNames) + var allCultures = content.AvailableCultures.Union(content.PublishedCultures); // union = distinct + foreach (var culture in allCultures) yield return new DocumentCultureVariationDto { NodeId = content.Id, LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."), Culture = culture, - // if not published, always edited - // no need to check for availability: it *is* available since it is in content.CultureNames - Edited = !content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture)) + Name = content.GetCultureName(culture) ?? content.GetPublishName(culture), + + // note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem + + Available = content.IsCultureAvailable(culture), + Published = content.IsCulturePublished(culture), + Edited = content.IsCultureAvailable(culture) && + (!content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture))) }; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 340eecb538..fb8c2732e6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -34,6 +35,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected IUmbracoDatabase Database => _scopeAccessor.AmbientScope.Database; protected Sql Sql() => _scopeAccessor.AmbientScope.SqlContext.Sql(); + protected ISqlSyntaxProvider SqlSyntax => _scopeAccessor.AmbientScope.SqlContext.SqlSyntax; #region Repository @@ -57,83 +59,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //fixme - we should be able to do sql = sql.OrderBy(x => Alias(x.NodeId, "NodeId")); but we can't because the OrderBy extension don't support Alias currently sql = sql.OrderBy("NodeId"); - //IEnumerable result; - // - //if (isMedia) - //{ - // //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! - // var pagedResult = UnitOfWork.Database.Page(pageIndex + 1, pageSize, pagedSql); - - // var ids = pagedResult.Items.Select(x => (int)x.id).InGroupsOf(2000); - // var entities = pagedResult.Items.Select(BuildEntityFromDynamic).Cast().ToList(); - - // //Now we need to merge in the property data since we need paging and we can't do this the way that the big media query was working before - // foreach (var idGroup in ids) - // { - // var propSql = GetPropertySql(Constants.ObjectTypes.Media) - // .WhereIn(x => x.NodeId, idGroup) - // .OrderBy(x => x.NodeId); - - // //This does NOT fetch all data into memory in a list, this will read - // // over the records as a data reader, this is much better for performance and memory, - // // but it means that during the reading of this data set, nothing else can be read - // // from SQL server otherwise we'll get an exception. - // var allPropertyData = UnitOfWork.Database.Query(propSql); - - // //keep track of the current property data item being enumerated - // var propertyDataSetEnumerator = allPropertyData.GetEnumerator(); - // var hasCurrent = false; // initially there is no enumerator.Current - - // try - // { - // //This must be sorted by node id (which is done by SQL) because this is how we are sorting the query to lookup property types above, - // // which allows us to more efficiently iterate over the large data set of property values. - // foreach (var entity in entities) - // { - // // assemble the dtos for this def - // // use the available enumerator.Current if any else move to next - // while (hasCurrent || propertyDataSetEnumerator.MoveNext()) - // { - // if (propertyDataSetEnumerator.Current.nodeId == entity.Id) - // { - // hasCurrent = false; // enumerator.Current is not available - - // //the property data goes into the additional data - // entity.AdditionalData[propertyDataSetEnumerator.Current.propertyTypeAlias] = new UmbracoEntity.EntityProperty - // { - // PropertyEditorAlias = propertyDataSetEnumerator.Current.propertyEditorAlias, - // Value = StringExtensions.IsNullOrWhiteSpace(propertyDataSetEnumerator.Current.textValue) - // ? propertyDataSetEnumerator.Current.varcharValue - // : StringExtensions.ConvertToJsonIfPossible(propertyDataSetEnumerator.Current.textValue) - // }; - // } - // else - // { - // hasCurrent = true; // enumerator.Current is available for another def - // break; // no more propertyDataDto for this def - // } - // } - // } - // } - // finally - // { - // propertyDataSetEnumerator.Dispose(); - // } - // } - - // result = entities; - //} - //else - //{ - // var pagedResult = UnitOfWork.Database.Page(pageIndex + 1, pageSize, pagedSql); - // result = pagedResult.Items.Select(BuildEntityFromDynamic).Cast().ToList(); - //} - var page = Database.Page(pageIndex + 1, pageSize, sql); var dtos = page.Items; var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray(); - - //TODO: For isContent will we need to build up the variation info? + + if (isContent) + BuildVariants(entities.Cast()); if (isMedia) BuildProperties(entities, dtos); @@ -154,11 +85,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //isContent is going to return a 1:M result now with the variants so we need to do different things if (isContent) { - var dtos = Database.FetchOneToMany( - ddto => ddto.VariationInfo, - ddto => ddto.VersionId, - sql); - return dtos.Count == 0 ? null : BuildDocumentEntity(dtos[0]); + var cdtos = Database.Fetch(sql); + + return cdtos.Count == 0 ? null : BuildVariants(BuildDocumentEntity(cdtos[0])); } var dto = Database.FirstOrDefault(sql); @@ -216,13 +145,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //isContent is going to return a 1:M result now with the variants so we need to do different things if (isContent) { - var cdtos = Database.FetchOneToMany( - dto => dto.VariationInfo, - dto => dto.VersionId, - sql); + var cdtos = Database.Fetch(sql); + return cdtos.Count == 0 ? Enumerable.Empty() - : cdtos.Select(BuildDocumentEntity).ToArray(); + : BuildVariants(cdtos.Select(BuildDocumentEntity)).ToList(); } var dtos = Database.Fetch(sql); @@ -323,7 +250,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private void BuildProperties(EntitySlim[] entities, List dtos) { - var versionIds = dtos.Select(x => x.VersionId).Distinct().ToArray(); + var versionIds = dtos.Select(x => x.VersionId).Distinct().ToList(); var pdtos = Database.FetchByGroups(versionIds, 2000, GetPropertyData); var xentity = entities.ToDictionary(x => x.Id, x => x); // nodeId -> entity @@ -346,10 +273,70 @@ namespace Umbraco.Core.Persistence.Repositories.Implement entity.AdditionalData[pdto.PropertyTypeDto.Alias] = new EntitySlim.PropertySlim(pdto.PropertyTypeDto.DataTypeDto.EditorAlias, value); } + private DocumentEntitySlim BuildVariants(DocumentEntitySlim entity) + => BuildVariants(new[] { entity }).First(); + + private IEnumerable BuildVariants(IEnumerable entities) + { + List v = null; + var entitiesList = entities.ToList(); + foreach (var e in entitiesList) + { + if (e.Variations.VariesByCulture()) + (v ?? (v = new List())).Add(e); + } + + if (v == null) return entitiesList; + + // fetch all variant info dtos + var dtos = Database.FetchByGroups(v.Select(x => x.Id), 2000, GetVariantInfos); + + // group by node id (each group contains all languages) + var xdtos = dtos.GroupBy(x => x.NodeId).ToDictionary(x => x.Key, x => x); + + foreach (var e in v) + { + // since we're only iterating on entities that vary, we must have something + var edtos = xdtos[e.Id]; + + e.CultureNames = edtos.Where(x => x.CultureAvailable).ToDictionary(x => x.IsoCode, x => x.Name); + e.PublishedCultures = edtos.Where(x => x.CulturePublished).Select(x => x.IsoCode); + e.EditedCultures = edtos.Where(x => x.CultureAvailable && x.CultureEdited).Select(x => x.IsoCode); + } + + return entitiesList; + } + #endregion #region Sql + protected Sql GetVariantInfos(IEnumerable ids) + { + return Sql() + .Select(x => x.NodeId) + .AndSelect(x => x.IsoCode) + .AndSelect("doc", x => Alias(x.Published, "DocumentPublished"), x => Alias(x.Edited, "DocumentEdited")) + .AndSelect("dcv", + x => Alias(x.Available, "CultureAvailable"), x => Alias(x.Published, "CulturePublished"), x => Alias(x.Edited, "CultureEdited"), + x => Alias(x.Name, "Name")) + + // from node x language + .From() + .CrossJoin() + + // join to document - always exists - indicates global document published/edited status + .InnerJoin("doc") + .On((node, doc) => node.NodeId == doc.NodeId, aliasRight: "doc") + + // left-join do document variation - matches cultures that are *available* + indicates when *edited* + .LeftJoin("dcv") + .On((node, dcv, lang) => node.NodeId == dcv.NodeId && lang.Id == dcv.LanguageId, aliasRight: "dcv") + + // for selected nodes + .WhereIn(x => x.NodeId, ids); + } + // gets the full sql for a given object type and a given unique id protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, Guid uniqueId) { @@ -371,24 +358,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return AddGroupBy(isContent, isMedia, sql); } - // fixme kill this nonsense - //// gets the SELECT + FROM + WHERE sql - //// to get all property data for all items of the specified object type - //private Sql GetPropertySql(Guid objectType) - //{ - // return Sql() - // .Select(x => x.VersionId, x => x.TextValue, x => x.VarcharValue) - // .AndSelect(x => x.NodeId) - // .AndSelect(x => x.PropertyEditorAlias) - // .AndSelect(x => Alias(x.Alias, "propertyTypeAlias")) - // .From() - // .InnerJoin().On((left, right) => left.VersionId == right.Id) - // .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - // .InnerJoin().On(dto => dto.PropertyTypeId, dto => dto.Id) - // .InnerJoin().On(dto => dto.DataTypeId, dto => dto.DataTypeId) - // .Where(x => x.NodeObjectType == objectType); - //} - private Sql GetPropertyData(int versionId) { return Sql() @@ -408,89 +377,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .InnerJoin().On((left, right) => left.DataTypeId == right.NodeId) .WhereIn(x => x.VersionId, versionIds) .OrderBy(x => x.VersionId); - } - - // fixme - wtf is this? - //private Sql GetFullSqlForMedia(Sql entitySql, Action> filter = null) - //{ - // //this will add any varcharValue property to the output which can be added to the additional properties - - // var sql = GetPropertySql(Constants.ObjectTypes.Media); - - // filter?.Invoke(sql); - - // // We're going to create a query to query against the entity SQL - // // because we cannot group by nText columns and we have a COUNT in the entitySql we cannot simply left join - // // the entitySql query, we have to join the wrapped query to get the ntext in the result - - // var wrappedSql = Sql() - // .Append("SELECT * FROM (") - // .Append(entitySql) - // .Append(") tmpTbl LEFT JOIN (") - // .Append(sql) - // .Append(") as property ON id = property.nodeId") - // .OrderBy("sortOrder, id"); - - // return wrappedSql; - //} - - - /// - /// The DTO used to fetch results for a content item with its variation info - /// - private class ContentEntityDto : BaseDto - { - public ContentVariation Variations { get; set; } - - [ResultColumn, Reference(ReferenceType.Many)] - public List VariationInfo { get; set; } - - public bool Published { get; set; } - public bool Edited { get; set; } - } - - /// - /// The DTO used in the 1:M result for content variation info - /// - private class ContentEntityVariationInfoDto - { - [Column("versionCultureId")] - public int VersionCultureId { get; set; } - [Column("versionCultureLangId")] - public int LanguageId { get; set; } - [Column("versionCultureName")] - public string Name { get; set; } - } - - // ReSharper disable once ClassNeverInstantiated.Local - /// - /// the DTO corresponding to fields selected by GetBase - /// - private class BaseDto - { - // ReSharper disable UnusedAutoPropertyAccessor.Local - // ReSharper disable UnusedMember.Local - public int NodeId { get; set; } - public bool Trashed { get; set; } - public int ParentId { get; set; } - public int? UserId { get; set; } - public int Level { get; set; } - public string Path { get; set; } - public int SortOrder { get; set; } - public Guid UniqueId { get; set; } - public string Text { get; set; } - public Guid NodeObjectType { get; set; } - public DateTime CreateDate { get; set; } - public int Children { get; set; } - public int VersionId { get; set; } - public string Alias { get; set; } - public string Icon { get; set; } - public string Thumbnail { get; set; } - public bool IsContainer { get; set; } - - // ReSharper restore UnusedAutoPropertyAccessor.Local - // ReSharper restore UnusedMember.Local - } + } // gets the base SELECT + FROM [+ filter] sql // always from the 'current' content version @@ -517,12 +404,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (isContent) { sql - .AndSelect(x => x.Published, x => x.Edited) - //This MUST come last in the select statements since we will end up with a 1:M query - .AndSelect( - x => Alias(x.Id, "versionCultureId"), - x => Alias(x.LanguageId, "versionCultureLangId"), - x => Alias(x.Name, "versionCultureName")); + .AndSelect(x => x.Published, x => x.Edited); } } @@ -534,7 +416,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .LeftJoin().On((left, right) => left.ContentTypeId == right.NodeId); + .InnerJoin().On((left, right) => left.ContentTypeId == right.NodeId); } if (isContent) @@ -548,10 +430,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { sql .LeftJoin("child").On((left, right) => left.NodeId == right.ParentId, aliasRight: "child"); - - if (isContent) - sql - .LeftJoin().On((left, right) => left.Id == right.VersionId); } @@ -613,8 +491,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (isContent) { sql - .AndBy(x => x.Published, x => x.Edited) - .AndBy(x => x.Id, x => x.LanguageId, x => x.Name); + .AndBy(x => x.Published, x => x.Edited); } @@ -649,184 +526,60 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public string TextValue { get; set; } } - // fixme kill + /// - /// This is a special relator in that it is not returning a DTO but a real resolved entity and that it accepts - /// a dynamic instance. + /// The DTO used to fetch results for a content item with its variation info /// - /// - /// We're doing this because when we query the db, we want to use dynamic so that it returns all available fields not just the ones - /// defined on the entity so we can them to additional data - /// - //internal class UmbracoEntityRelator - //{ - // internal UmbracoEntity Current; + private class ContentEntityDto : BaseDto + { + public ContentVariation Variations { get; set; } - // public IEnumerable MapAll(IEnumerable input) - // { - // UmbracoEntity entity; + public bool Published { get; set; } + public bool Edited { get; set; } + } - // foreach (var x in input) - // { - // entity = Map(x); - // if (entity != null) yield return entity; - // } + public class VariantInfoDto + { + public int NodeId { get; set; } + public string IsoCode { get; set; } + public string Name { get; set; } + public bool DocumentPublished { get; set; } + public bool DocumentEdited { get; set; } - // entity = Map((dynamic) null); - // if (entity != null) yield return entity; - // } + public bool CultureAvailable { get; set; } + public bool CulturePublished { get; set; } + public bool CultureEdited { get; set; } + } - // // must be called one last time with null in order to return the last one! - // public UmbracoEntity Map(dynamic a) - // { - // // Terminating call. Since we can return null from this function - // // we need to be ready for NPoco to callback later with null - // // parameters - // if (a == null) - // return Current; - - // string pPropertyEditorAlias = a.propertyEditorAlias; - // var pExists = pPropertyEditorAlias != null; - // string pPropertyAlias = a.propertyTypeAlias; - // string pTextValue = a.textValue; - // string pNVarcharValue = a.varcharValue; - - // // Is this the same UmbracoEntity as the current one we're processing - // if (Current != null && Current.Key == a.uniqueID) - // { - // if (pExists && pPropertyAlias.IsNullOrWhiteSpace() == false) - // { - // // Add this UmbracoProperty to the current additional data - // Current.AdditionalData[pPropertyAlias] = new UmbracoEntity.EntityProperty - // { - // PropertyEditorAlias = pPropertyEditorAlias, - // Value = pTextValue.IsNullOrWhiteSpace() - // ? pNVarcharValue - // : pTextValue.ConvertToJsonIfPossible() - // }; - // } - - // // Return null to indicate we're not done with this UmbracoEntity yet - // return null; - // } - - // // This is a different UmbracoEntity to the current one, or this is the - // // first time through and we don't have a Tab yet - - // // Save the current UmbracoEntityDto - // var prev = Current; - - // // Setup the new current UmbracoEntity - - // Current = BuildEntityFromDynamic(a); - - // if (pExists && pPropertyAlias.IsNullOrWhiteSpace() == false) - // { - // //add the property/create the prop list if null - // Current.AdditionalData[pPropertyAlias] = new UmbracoEntity.EntityProperty - // { - // PropertyEditorAlias = pPropertyEditorAlias, - // Value = pTextValue.IsNullOrWhiteSpace() - // ? pNVarcharValue - // : pTextValue.ConvertToJsonIfPossible() - // }; - // } - - // // Return the now populated previous UmbracoEntity (or null if first time through) - // return prev; - // } - //} - - // fixme need to review what's below - // comes from 7.6, similar to what's in VersionableRepositoryBase - // not sure it really makes sense... - - //private class EntityDefinitionCollection : KeyedCollection - //{ - // protected override int GetKeyForItem(EntityDefinition item) - // { - // return item.Id; - // } - - // /// - // /// if this key already exists if it does then we need to check - // /// if the existing item is 'older' than the new item and if that is the case we'll replace the older one - // /// - // /// - // /// - // public bool AddOrUpdate(EntityDefinition item) - // { - // if (Dictionary == null) - // { - // Add(item); - // return true; - // } - - // var key = GetKeyForItem(item); - // if (TryGetValue(key, out EntityDefinition found)) - // { - // //it already exists and it's older so we need to replace it - // if (item.VersionId > found.VersionId) - // { - // var currIndex = Items.IndexOf(found); - // if (currIndex == -1) - // throw new IndexOutOfRangeException("Could not find the item in the list: " + found.Id); - - // //replace the current one with the newer one - // SetItem(currIndex, item); - // return true; - // } - // //could not add or update - // return false; - // } - - // Add(item); - // return true; - // } - - // private bool TryGetValue(int key, out EntityDefinition val) - // { - // if (Dictionary != null) return Dictionary.TryGetValue(key, out val); - - // val = null; - // return false; - // } - //} - - // fixme wtf is this, why dynamics here, this is horrible !! - //private class EntityDefinition - //{ - // private readonly dynamic _entity; - // private readonly bool _isContent; - // private readonly bool _isMedia; - - // public EntityDefinition(dynamic entity, bool isContent, bool isMedia) - // { - // _entity = entity; - // _isContent = isContent; - // _isMedia = isMedia; - // } - - // public IUmbracoEntity BuildFromDynamic() - // { - // return BuildEntityFromDynamic(_entity); - // } - - // public int Id => _entity.id; - - // public int VersionId - // { - // get - // { - // if (_isContent || _isMedia) - // { - // return _entity.versionId; - // } - // return _entity.id; - // } - // } - //} + // ReSharper disable once ClassNeverInstantiated.Local + /// + /// the DTO corresponding to fields selected by GetBase + /// + private class BaseDto + { + // ReSharper disable UnusedAutoPropertyAccessor.Local + // ReSharper disable UnusedMember.Local + public int NodeId { get; set; } + public bool Trashed { get; set; } + public int ParentId { get; set; } + public int? UserId { get; set; } + public int Level { get; set; } + public string Path { get; set; } + public int SortOrder { get; set; } + public Guid UniqueId { get; set; } + public string Text { get; set; } + public Guid NodeObjectType { get; set; } + public DateTime CreateDate { get; set; } + public int Children { get; set; } + public int VersionId { get; set; } + public string Alias { get; set; } + public string Icon { get; set; } + public string Thumbnail { get; set; } + public bool IsContainer { get; set; } + // ReSharper restore UnusedAutoPropertyAccessor.Local + // ReSharper restore UnusedMember.Local + } #endregion #region Factory @@ -879,44 +632,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private DocumentEntitySlim BuildDocumentEntity(BaseDto dto) { + // EntitySlim does not track changes + var entity = new DocumentEntitySlim(); + BuildContentEntity(entity, dto); + if (dto is ContentEntityDto contentDto) { - return BuildDocumentEntity(contentDto); + // fill in the invariant info + entity.Edited = contentDto.Edited; + entity.Published = contentDto.Published; + entity.Variations = contentDto.Variations; } - // EntitySlim does not track changes - var entity = new DocumentEntitySlim(); - BuildContentEntity(entity, dto); - return entity; - } - - /// - /// Builds the from a and ensures the AdditionalData is populated with variant info - /// - /// - /// - private DocumentEntitySlim BuildDocumentEntity(ContentEntityDto dto) - { - // EntitySlim does not track changes - var entity = new DocumentEntitySlim(); - BuildContentEntity(entity, dto); - - //fixme we need to set these statuses for each variant, see notes in IDocumentEntitySlim - entity.Edited = dto.Edited; - entity.Published = dto.Published; - - if (dto.Variations.VariesByCulture() && dto.VariationInfo != null && dto.VariationInfo.Count > 0) - { - var variantInfo = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - foreach (var info in dto.VariationInfo) - { - var isoCode = _langRepository.GetIsoCodeById(info.LanguageId); - if (isoCode != null) - variantInfo[isoCode] = info.Name; - } - entity.CultureNames = variantInfo; - entity.Variations = dto.Variations; - } return entity; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs index 740015683a..2b3674700b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs @@ -54,11 +54,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // get languages var languages = Database.Fetch(sql).Select(ConvertFromDto).OrderBy(x => x.Id).ToList(); - // fix inconsistencies: there has to be a default language, and it has to be mandatory - var defaultLanguage = languages.FirstOrDefault(x => x.IsDefaultVariantLanguage) ?? languages.First(); - defaultLanguage.IsDefaultVariantLanguage = true; - defaultLanguage.Mandatory = true; - // initialize the code-id map lock (_codeIdMap) { @@ -79,7 +74,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - return Database.Fetch(sql).Select(ConvertFromDto); + var dtos = Database.Fetch(sql); + return dtos.Select(ConvertFromDto).ToList(); } #endregion @@ -120,10 +116,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return list; } - protected override Guid NodeObjectTypeId - { - get { throw new NotImplementedException(); } - } + protected override Guid NodeObjectTypeId => throw new NotImplementedException(); #endregion @@ -131,70 +124,95 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(ILanguage entity) { - if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureInfo == null || entity.CultureName.IsNullOrWhiteSpace()) - throw new InvalidOperationException("The required language data is missing"); + // validate iso code and culture name + if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureName.IsNullOrWhiteSpace()) + throw new InvalidOperationException("Cannot save a language without an ISO code and a culture name."); - ((EntityBase)entity).AddingEntity(); + ((EntityBase) entity).AddingEntity(); - if (entity.IsDefaultVariantLanguage) + // deal with entity becoming the new default entity + if (entity.IsDefault) { - //if this entity is flagged as the default, we need to set all others to false - Database.Execute(Sql().Update(u => u.Set(x => x.IsDefaultVariantLanguage, false))); - //We need to clear the whole cache since all languages will be updated - IsolatedCache.ClearAllCache(); + // set all other entities to non-default + // safe (no race cond) because the service locks languages + var setAllDefaultToFalse = Sql() + .Update(u => u.Set(x => x.IsDefault, false)); + Database.Execute(setAllDefaultToFalse); } -; - var dto = LanguageFactory.BuildDto(entity); + // fallback cycles are detected at service level + // insert + var dto = LanguageFactory.BuildDto(entity); var id = Convert.ToInt32(Database.Insert(dto)); entity.Id = id; - entity.ResetDirtyProperties(); - } protected override void PersistUpdatedItem(ILanguage entity) { - if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureInfo == null || entity.CultureName.IsNullOrWhiteSpace()) - throw new InvalidOperationException("The required language data is missing"); + // validate iso code and culture name + if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureName.IsNullOrWhiteSpace()) + throw new InvalidOperationException("Cannot save a language without an ISO code and a culture name."); - ((EntityBase)entity).UpdatingEntity(); + ((EntityBase) entity).UpdatingEntity(); - if (entity.IsDefaultVariantLanguage) + if (entity.IsDefault) { - //if this entity is flagged as the default, we need to set all others to false - Database.Execute(Sql().Update(u => u.Set(x => x.IsDefaultVariantLanguage, false))); - //We need to clear the whole cache since all languages will be updated - IsolatedCache.ClearAllCache(); + // deal with entity becoming the new default entity + + // set all other entities to non-default + // safe (no race cond) because the service locks languages + var setAllDefaultToFalse = Sql() + .Update(u => u.Set(x => x.IsDefault, false)); + Database.Execute(setAllDefaultToFalse); } - + else + { + // deal with the entity not being default anymore + // which is illegal - another entity has to become default + var selectDefaultId = Sql() + .Select(x => x.Id) + .From() + .Where(x => x.IsDefault); + + var defaultId = Database.ExecuteScalar(selectDefaultId); + if (entity.Id == defaultId) + throw new InvalidOperationException($"Cannot save the default language ({entity.IsoCode}) as non-default. Make another language the default language instead."); + } + + // fallback cycles are detected at service level + + // update var dto = LanguageFactory.BuildDto(entity); - Database.Update(dto); - entity.ResetDirtyProperties(); - - //Clear the cache entries that exist by key/iso - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(entity.IsoCode)); - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(entity.CultureName)); } protected override void PersistDeletedItem(ILanguage entity) { - //we need to validate that we can delete this language - if (entity.IsDefaultVariantLanguage) - throw new InvalidOperationException($"Cannot delete the default language ({entity.IsoCode})"); + // validate that the entity is not the default language. + // safe (no race cond) because the service locks languages - var count = Database.ExecuteScalar(Sql().SelectCount().From()); - if (count == 1) - throw new InvalidOperationException($"Cannot delete the default language ({entity.IsoCode})"); + var selectDefaultId = Sql() + .Select(x => x.Id) + .From() + .Where(x => x.IsDefault); + var defaultId = Database.ExecuteScalar(selectDefaultId); + if (entity.Id == defaultId) + throw new InvalidOperationException($"Cannot delete the default language ({entity.IsoCode})."); + + // We need to remove any references to the language if it's being used as a fall-back from other ones + var clearFallbackLanguage = Sql() + .Update(u => u + .Set(x => x.FallbackLanguageId, null)) + .Where(x => x.FallbackLanguageId == entity.Id); + + Database.Execute(clearFallbackLanguage); + + // delete base.PersistDeletedItem(entity); - - //Clear the cache entries that exist by key/iso - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(entity.IsoCode)); - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(entity.CultureName)); } #endregion @@ -204,7 +222,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var entity = LanguageFactory.BuildEntity(dto); return entity; } - + public ILanguage GetByIsoCode(string isoCode) { TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way @@ -262,17 +280,20 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private ILanguage GetDefault() { // get all cached, non-cloned - var all = TypedCachePolicy.GetAllCached(PerformGetAll); + var languages = TypedCachePolicy.GetAllCached(PerformGetAll).ToList(); + var language = languages.FirstOrDefault(x => x.IsDefault); + if (language != null) return language; + + // this is an anomaly, the service/repo should ensure it cannot happen + Logger.Warn("There is no default language. Fix this anomaly by editing the language table in database and setting one language as the default language."); + + // still, don't kill the site, and return "something" ILanguage first = null; - foreach (var language in all) + foreach (var l in languages) { - // if one language is default, return - if (language.IsDefaultVariantLanguage) - return language; - // keep track of language with lowest id - if (first == null || language.Id < first.Id) - first = language; + if (first == null || l.Id < first.Id) + first = l; } return first; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 982a5bb885..2390ce9a7b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -136,7 +137,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { var list = new List { - "DELETE FROM " + Constants.DatabaseSchema.Tables.Task + " WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.User2NodeNotify + " WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.UserGroup2NodePermission + " WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.UserStartNode + " WHERE startNode = @id", @@ -455,11 +455,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion - /// - /// Gets paged media results. - /// - public override IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null) + /// + public override IEnumerable GetPage(IQuery query, + long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering) { Sql filterSql = null; @@ -471,8 +470,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } return GetPage(query, pageIndex, pageSize, out totalRecords, - x => MapDtosToContent(x), orderBy, orderDirection, orderBySystemField, - filterSql); + x => MapDtosToContent(x), + filterSql, + ordering); } private IEnumerable MapDtosToContent(List dtos, bool withCache = false) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 98c38603b1..84ef154ae8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -174,7 +175,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { var list = new List { - "DELETE FROM cmsTask WHERE nodeId = @id", "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @id", "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @id", "DELETE FROM umbracoRelation WHERE parentId = @id", @@ -492,8 +492,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Gets paged member results. /// - public override IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null) + public override IEnumerable GetPage(IQuery query, + long pageIndex, int pageSize, out long totalRecords, + IQuery filter, + Ordering ordering) { Sql filterSql = null; @@ -505,8 +507,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } return GetPage(query, pageIndex, pageSize, out totalRecords, - x => MapDtosToContent(x), orderBy, orderDirection, orderBySystemField, - filterSql); + x => MapDtosToContent(x), + filterSql, + ordering); } private string _pagedResultsByQueryWhere; @@ -523,20 +526,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return _pagedResultsByQueryWhere; } - protected override string GetDatabaseFieldNameForOrderBy(string orderBy) + protected override string ApplySystemOrdering(ref Sql sql, Ordering ordering) { - //Some custom ones - switch (orderBy.ToUpperInvariant()) - { - case "EMAIL": - return GetDatabaseFieldNameForOrderBy("cmsMember", "email"); - case "LOGINNAME": - return GetDatabaseFieldNameForOrderBy("cmsMember", "loginName"); - case "USERNAME": - return GetDatabaseFieldNameForOrderBy("cmsMember", "loginName"); - } + if (ordering.OrderBy.InvariantEquals("email")) + return SqlSyntax.GetFieldName(x => x.Email); - return base.GetDatabaseFieldNameForOrderBy(orderBy); + if (ordering.OrderBy.InvariantEquals("loginName")) + return SqlSyntax.GetFieldName(x => x.LoginName); + + if (ordering.OrderBy.InvariantEquals("userName")) + return SqlSyntax.GetFieldName(x => x.LoginName); + + return base.ApplySystemOrdering(ref sql, ordering); } private IEnumerable MapDtosToContent(List dtos, bool withCache = false) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/TaskRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/TaskRepository.cs deleted file mode 100644 index 1b44fd8cc8..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/TaskRepository.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NPoco; -using Umbraco.Core.Cache; -using Umbraco.Core.Exceptions; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Scoping; - -namespace Umbraco.Core.Persistence.Repositories.Implement -{ - internal class TaskRepository : NPocoRepositoryBase, ITaskRepository - { - public TaskRepository(IScopeAccessor scopeAccessor, ILogger logger) - : base(scopeAccessor, CacheHelper.NoCache, logger) - { } - - protected override Task PerformGet(int id) - { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { id = id }); - - var taskDto = Database.Fetch(SqlContext.SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); - if (taskDto == null) - return null; - - var entity = TaskFactory.BuildEntity(taskDto); - return entity; - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - var sql = GetBaseQuery(false); - - if (ids.Any()) - { - sql.Where("cmsTask.id IN (@ids)", new { ids = ids }); - } - - var dtos = Database.Fetch(sql); - return dtos.Select(TaskFactory.BuildEntity); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); - - var dtos = Database.Fetch(sql); - return dtos.Select(TaskFactory.BuildEntity); - } - - protected override Sql GetBaseQuery(bool isCount) - { - return isCount ? SqlContext.Sql().SelectCount().From() : GetBaseQuery(); - } - - private Sql GetBaseQuery() - { - return SqlContext.Sql() - .Select("cmsTask.closed,cmsTask.id,cmsTask.taskTypeId,cmsTask.nodeId,cmsTask.parentUserId,cmsTask.userId,cmsTask." + SqlContext.SqlSyntax.GetQuotedColumnName("DateTime") + ",cmsTask.Comment,cmsTaskType.id, cmsTaskType.alias") - .From() - .InnerJoin() - .On(left => left.TaskTypeId, right => right.Id) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId); - } - - protected override string GetBaseWhereClause() - { - return "cmsTask.id = @id"; - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM cmsTask WHERE id = @id" - }; - return list; - } - - protected override Guid NodeObjectTypeId => throw new WontImplementException(); - - protected override void PersistNewItem(Task entity) - { - entity.AddingEntity(); - - //ensure the task type exists - var taskType = Database.SingleOrDefault("Where alias = @alias", new { alias = entity.TaskType.Alias }); - if (taskType == null) - { - var taskTypeId = Convert.ToInt32(Database.Insert(new TaskTypeDto { Alias = entity.TaskType.Alias })); - entity.TaskType.Id = taskTypeId; - } - else - { - entity.TaskType.Id = taskType.Id; - } - - var dto = TaskFactory.BuildDto(entity); - - var id = Convert.ToInt32(Database.Insert(dto)); - entity.Id = id; - - entity.ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(Task entity) - { - entity.UpdatingEntity(); - - var dto = TaskFactory.BuildDto(entity); - - Database.Update(dto); - - entity.ResetDirtyProperties(); - } - - public IEnumerable GetTasks(int? itemId = null, int? assignedUser = null, int? ownerUser = null, string taskTypeAlias = null, bool includeClosed = false) - { - var sql = GetGetTasksQuery(assignedUser, ownerUser, taskTypeAlias, includeClosed); - if (itemId.HasValue) - { - sql.Where(dto => dto.NodeId == itemId.Value); - } - - var dtos = Database.Fetch(sql); - - return dtos.Select(TaskFactory.BuildEntity); - } - - private Sql GetGetTasksQuery(int? assignedUser = null, int? ownerUser = null, string taskTypeAlias = null, bool includeClosed = false) - { - var sql = GetBaseQuery(false); - - if (includeClosed == false) - { - sql.Where(dto => dto.Closed == false); - } - if (taskTypeAlias.IsNullOrWhiteSpace() == false) - { - sql.Where("cmsTaskType.alias = @alias", new { alias = taskTypeAlias }); - } - if (ownerUser.HasValue) - { - sql.Where(dto => dto.ParentUserId == ownerUser.Value); - } - if (assignedUser.HasValue) - { - sql.Where(dto => dto.UserId == assignedUser.Value); - } - return sql; - } - } -} diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/TaskTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/TaskTypeRepository.cs deleted file mode 100644 index ada49b4ec7..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/TaskTypeRepository.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NPoco; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Scoping; - -namespace Umbraco.Core.Persistence.Repositories.Implement -{ - internal class TaskTypeRepository : NPocoRepositoryBase, ITaskTypeRepository - { - public TaskTypeRepository(IScopeAccessor scopeAccessor, ILogger logger) - : base(scopeAccessor, CacheHelper.NoCache, logger) - { } - - protected override TaskType PerformGet(int id) - { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - - var taskDto = Database.Fetch(SqlContext.SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); - if (taskDto == null) - return null; - - var entity = TaskTypeFactory.BuildEntity(taskDto); - return entity; - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - var sql = GetBaseQuery(false); - - if (ids.Any()) - { - sql.Where("cmsTaskType.id IN (@ids)", new { ids }); - } - - var dtos = Database.Fetch(sql); - return dtos.Select(TaskTypeFactory.BuildEntity); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); - - var dtos = Database.Fetch(sql); - return dtos.Select(TaskTypeFactory.BuildEntity); - } - - protected override Sql GetBaseQuery(bool isCount) - { - return SqlContext.Sql().SelectAll().From(); - } - - protected override string GetBaseWhereClause() - { - return "cmsTaskType.id = @id"; - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM cmsTask WHERE taskTypeId = @id", - "DELETE FROM cmsTaskType WHERE id = @id" - }; - return list; - } - - protected override Guid NodeObjectTypeId => throw new NotImplementedException(); - - protected override void PersistNewItem(TaskType entity) - { - entity.AddingEntity(); - - //TODO: Just remove the task type db table or add a unique index to the alias - - //ensure the task type exists - var taskType = Database.SingleOrDefault("Where alias = @alias", new { alias = entity.Alias }); - if (taskType != null) - { - throw new InvalidOperationException("A task type already exists with the given alias " + entity.Alias); - } - - var dto = TaskTypeFactory.BuildDto(entity); - - var id = Convert.ToInt32(Database.Insert(dto)); - entity.Id = id; - - entity.ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(TaskType entity) - { - entity.UpdatingEntity(); - - var dto = TaskTypeFactory.BuildDto(entity); - - Database.Update(dto); - - entity.ResetDirtyProperties(); - } - } -} diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs index ba4e22de41..6a2c81c6b5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs @@ -629,72 +629,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } } - /// - /// Returns a template as a template node which can be traversed (parent, children) - /// - /// - /// - [Obsolete("Use GetDescendants instead")] - public TemplateNode GetTemplateNode(string alias) - { - //first get all template objects - var allTemplates = base.GetMany().ToArray(); - - var selfTemplate = allTemplates.SingleOrDefault(x => x.Alias.InvariantEquals(alias)); - if (selfTemplate == null) - { - return null; - } - - var top = selfTemplate; - while (top.MasterTemplateAlias.IsNullOrWhiteSpace() == false) - { - top = allTemplates.Single(x => x.Alias.InvariantEquals(top.MasterTemplateAlias)); - } - - var topNode = new TemplateNode(allTemplates.Single(x => x.Id == top.Id)); - var childTemplates = allTemplates.Where(x => x.MasterTemplateAlias.InvariantEquals(top.Alias)); - //This now creates the hierarchy recursively - topNode.Children = CreateChildren(topNode, childTemplates, allTemplates); - - //now we'll return the TemplateNode requested - return FindTemplateInTree(topNode, alias); - } - - [Obsolete("Only used by obsolete code")] - private static TemplateNode WalkTree(TemplateNode current, string alias) - { - //now walk the tree to find the node - if (current.Template.Alias.InvariantEquals(alias)) - { - return current; - } - foreach (var c in current.Children) - { - var found = WalkTree(c, alias); - if (found != null) return found; - } - return null; - } - - /// - /// Given a template node in a tree, this will find the template node with the given alias if it is found in the hierarchy, otherwise null - /// - /// - /// - /// - [Obsolete("Use GetDescendants instead")] - public TemplateNode FindTemplateInTree(TemplateNode anyNode, string alias) - { - //first get the root - var top = anyNode; - while (top.Parent != null) - { - top = top.Parent; - } - return WalkTree(top, alias); - } - /// /// This checks what the default rendering engine is set in config but then also ensures that there isn't already /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 0ec5f9353b..23c7c055f8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -150,6 +150,8 @@ UNION SELECT '4CountOfLockedOut' AS colName, COUNT(id) AS num FROM umbracoUser WHERE userNoConsole = 1 UNION SELECT '5CountOfInvited' AS colName, COUNT(id) AS num FROM umbracoUser WHERE lastLoginDate IS NULL AND userDisabled = 1 AND invitedDate IS NOT NULL +UNION +SELECT '6CountOfDisabled' AS colName, COUNT(id) AS num FROM umbracoUser WHERE userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NULL ORDER BY colName"; var result = Database.Fetch(sql); @@ -160,7 +162,8 @@ ORDER BY colName"; {UserState.Active, (int) result[1].num}, {UserState.Disabled, (int) result[2].num}, {UserState.LockedOut, (int) result[3].num}, - {UserState.Invited, (int) result[4].num} + {UserState.Invited, (int) result[4].num}, + {UserState.Inactive, (int) result[5].num} }; } @@ -418,8 +421,6 @@ ORDER BY colName"; { var list = new List { - "DELETE FROM cmsTask WHERE userId = @id", - "DELETE FROM cmsTask WHERE parentUserId = @id", "DELETE FROM umbracoUser2UserGroup WHERE userId = @id", "DELETE FROM umbracoUser2NodeNotify WHERE userId = @id", "DELETE FROM umbracoUser WHERE id = @id", @@ -745,7 +746,7 @@ ORDER BY colName"; if (excludeUserGroups != null && excludeUserGroups.Length > 0) { - var subQuery = @"AND (umbracoUser.id NOT IN (SELECT DISTINCT umbracoUser.id + const string subQuery = @"AND (umbracoUser.id NOT IN (SELECT DISTINCT umbracoUser.id FROM umbracoUser INNER JOIN umbracoUser2UserGroup ON umbracoUser2UserGroup.userId = umbracoUser.id INNER JOIN umbracoUserGroup ON umbracoUserGroup.id = umbracoUser2UserGroup.userGroupId @@ -766,6 +767,12 @@ ORDER BY colName"; sb.Append("(userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NOT NULL)"); appended = true; } + if (userState.Contains(UserState.Inactive)) + { + if (appended) sb.Append(" OR "); + sb.Append("(userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NULL)"); + appended = true; + } if (userState.Contains(UserState.Disabled)) { if (appended) sb.Append(" OR "); @@ -800,7 +807,7 @@ ORDER BY colName"; sql = new SqlTranslator(sql, query).Translate(); // get sorted and filtered sql - var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql), orderDirection, orderBy); + var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql, query != null), orderDirection, orderBy); // get a page of results and total count var pagedResult = Database.Page(pageIndex + 1, pageSize, sqlNodeIdsWithSort); @@ -811,11 +818,17 @@ ORDER BY colName"; return pagedResult.Items.Select(UserFactory.BuildEntity); } - private Sql ApplyFilter(Sql sql, Sql filterSql) + private Sql ApplyFilter(Sql sql, Sql filterSql, bool hasWhereClause) { if (filterSql == null) return sql; - sql.Append(SqlContext.Sql(" WHERE " + filterSql.SQL.TrimStart("AND "), filterSql.Arguments)); + //ensure we don't append a WHERE if there is already one + var args = filterSql.Arguments; + var sqlFilter = hasWhereClause + ? filterSql.SQL + : " WHERE " + filterSql.SQL.TrimStart("AND "); + + sql.Append(SqlContext.Sql(sqlFilter, args)); return sql; } diff --git a/src/Umbraco.Core/Persistence/SqlContextExtensions.cs b/src/Umbraco.Core/Persistence/SqlContextExtensions.cs new file mode 100644 index 0000000000..e28816b6a4 --- /dev/null +++ b/src/Umbraco.Core/Persistence/SqlContextExtensions.cs @@ -0,0 +1,78 @@ +using System; +using System.Linq.Expressions; +using Umbraco.Core.Persistence.Querying; + +namespace Umbraco.Core.Persistence +{ + /// + /// Provides extension methods to . + /// + public static class SqlContextExtensions + { + /// + /// Visit an expression. + /// + /// The type of the DTO. + /// An . + /// An expression to visit. + /// An optional table alias. + /// A SQL statement, and arguments, corresponding to the expression. + public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias = null) + { + var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias); + var visited = expresionist.Visit(expression); + return (visited, expresionist.GetSqlParameters()); + } + + /// + /// Visit an expression. + /// + /// The type of the DTO. + /// The type returned by the expression. + /// An . + /// An expression to visit. + /// An optional table alias. + /// A SQL statement, and arguments, corresponding to the expression. + public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias = null) + { + var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias); + var visited = expresionist.Visit(expression); + return (visited, expresionist.GetSqlParameters()); + } + + /// + /// Visit an expression. + /// + /// The type of the first DTO. + /// The type of the second DTO. + /// An . + /// An expression to visit. + /// An optional table alias for the first DTO. + /// An optional table alias for the second DTO. + /// A SQL statement, and arguments, corresponding to the expression. + public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) + { + var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); + var visited = expresionist.Visit(expression); + return (visited, expresionist.GetSqlParameters()); + } + + /// + /// Visit an expression. + /// + /// The type of the first DTO. + /// The type of the second DTO. + /// The type returned by the expression. + /// An . + /// An expression to visit. + /// An optional table alias for the first DTO. + /// An optional table alias for the second DTO. + /// A SQL statement, and arguments, corresponding to the expression. + public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) + { + var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); + var visited = expresionist.Visit(expression); + return (visited, expresionist.GetSqlParameters()); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index af58604bf0..98c57644aa 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -44,9 +44,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax string TruncateTable { get; } string CreateConstraint { get; } string DeleteConstraint { get; } - - [Obsolete("This is never used, use the Format(ForeignKeyDefinition) instead")] - string CreateForeignKeyConstraint { get; } + string DeleteDefaultConstraint { get; } string FormatDateTime(DateTime date, bool includeTime = true); string Format(TableDefinition table); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 9c6e95971c..90a2215e3d 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data.Common; using System.Linq; using NPoco; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -45,6 +46,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax V2012 = 5, V2014 = 6, V2016 = 7, + V2017 = 8, Other = 99 } @@ -71,37 +73,35 @@ namespace Umbraco.Core.Persistence.SqlSyntax public void Initialize() { - var firstPart = string.IsNullOrWhiteSpace(ProductVersion) ? "??" : ProductVersion.Split('.')[0]; - switch (firstPart) - { - case "??": - ProductVersionName = VersionName.Invalid; - break; - case "13": - ProductVersionName = VersionName.V2016; - break; - case "12": - ProductVersionName = VersionName.V2014; - break; - case "11": - ProductVersionName = VersionName.V2012; - break; - case "10": - ProductVersionName = VersionName.V2008; - break; - case "9": - ProductVersionName = VersionName.V2005; - break; - case "8": - ProductVersionName = VersionName.V2000; - break; - case "7": - ProductVersionName = VersionName.V7; - break; - default: - ProductVersionName = VersionName.Other; - break; - } + ProductVersionName = MapProductVersion(ProductVersion); + } + } + + private static VersionName MapProductVersion(string productVersion) + { + var firstPart = string.IsNullOrWhiteSpace(productVersion) ? "??" : productVersion.Split('.')[0]; + switch (firstPart) + { + case "??": + return VersionName.Invalid; + case "14": + return VersionName.V2017; + case "13": + return VersionName.V2016; + case "12": + return VersionName.V2014; + case "11": + return VersionName.V2012; + case "10": + return VersionName.V2008; + case "9": + return VersionName.V2005; + case "8": + return VersionName.V2000; + case "7": + return VersionName.V7; + default: + return VersionName.Other; } } @@ -136,6 +136,33 @@ namespace Umbraco.Core.Persistence.SqlSyntax } } + internal static VersionName GetVersionName(string connectionString, string providerName) + { + var factory = DbProviderFactories.GetFactory(providerName); + var connection = factory.CreateConnection(); + + if (connection == null) + throw new InvalidOperationException($"Could not create a connection for provider \"{providerName}\"."); + + connection.ConnectionString = connectionString; + using (connection) + { + try + { + connection.Open(); + var command = connection.CreateCommand(); + command.CommandText = "SELECT SERVERPROPERTY('ProductVersion');"; + var productVersion = command.ExecuteScalar().ToString(); + connection.Close(); + return MapProductVersion(productVersion); + } + catch + { + return VersionName.Unknown; + } + } + } + /// /// SQL Server stores default values assigned to columns as constraints, it also stores them with named values, this is the only /// server type that does this, therefore this method doesn't exist on any other syntax provider @@ -143,19 +170,19 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// public IEnumerable> GetDefaultConstraintsPerColumn(IDatabase db) { - var items = db.Fetch("SELECT TableName = t.Name,ColumnName = c.Name,dc.Name,dc.[Definition] FROM sys.tables t INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id"); + var items = db.Fetch("SELECT TableName = t.Name, ColumnName = c.Name, dc.Name, dc.[Definition] FROM sys.tables t INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id INNER JOIN sys.schemas as s on t.[schema_id] = s.[schema_id] WHERE s.name = (SELECT SCHEMA_NAME())"); return items.Select(x => new Tuple(x.TableName, x.ColumnName, x.Name, x.Definition)); } public override IEnumerable GetTablesInSchema(IDatabase db) { - var items = db.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"); + var items = db.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())"); return items.Select(x => x.TABLE_NAME).Cast().ToList(); } public override IEnumerable GetColumnsInSchema(IDatabase db) { - var items = db.Fetch("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS"); + var items = db.Fetch("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())"); return items.Select( item => @@ -168,7 +195,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax { var items = db.Fetch( - "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE"); + "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())"); return items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); } @@ -177,7 +204,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax { var items = db.Fetch( - "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE"); + "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())"); return items.Select(item => new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)).ToList(); } @@ -191,7 +218,8 @@ CASE WHEN I.is_unique_constraint = 1 OR I.is_unique = 1 THEN 1 ELSE 0 END AS [U from sys.tables as T inner join sys.indexes as I on T.[object_id] = I.[object_id] inner join sys.index_columns as IC on IC.[object_id] = I.[object_id] and IC.[index_id] = I.[index_id] inner join sys.all_columns as AC on IC.[object_id] = AC.[object_id] and IC.[column_id] = AC.[column_id] -WHERE I.is_primary_key = 0 + inner join sys.schemas as S on T.[schema_id] = S.[schema_id] +WHERE S.name = (SELECT SCHEMA_NAME()) AND I.is_primary_key = 0 order by T.name, I.name"); return items.Select(item => new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE == 1)).ToList(); @@ -201,7 +229,7 @@ order by T.name, I.name"); public override bool DoesTableExist(IDatabase db, string tableName) { var result = - db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @TableName", + db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @TableName AND TABLE_SCHEMA = (SELECT SCHEMA_NAME())", new { TableName = tableName }); return result > 0; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index f8c937ce8d..afab5b19f9 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -47,7 +47,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax var col = Regex.Escape(GetQuotedColumnName("column")).Replace("column", @"\w+"); var fld = Regex.Escape(GetQuotedTableName("table") + ".").Replace("table", @"\w+") + col; // ReSharper restore VirtualMemberCallInConstructor - AliasRegex = new Regex("(" + fld + @")\s+AS\s+(" + col + ")", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase); + AliasRegex = new Regex("(" + fld + @")\s+AS\s+(" + col + ")", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); } public Regex AliasRegex { get; } diff --git a/src/Umbraco.Core/Persistence/SqlSyntaxExtensions.cs b/src/Umbraco.Core/Persistence/SqlSyntaxExtensions.cs new file mode 100644 index 0000000000..43ef03327b --- /dev/null +++ b/src/Umbraco.Core/Persistence/SqlSyntaxExtensions.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; +using NPoco; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence +{ + /// + /// Provides extension methods to . + /// + public static class SqlSyntaxExtensions + { + private static string GetTableName(this Type type) + { + // todo: returning string.Empty for now + // BUT the code bits that calls this method cannot deal with string.Empty so we + // should either throw, or fix these code bits... + var attr = type.FirstAttribute(); + return string.IsNullOrWhiteSpace(attr?.Value) ? string.Empty : attr.Value; + } + + private static string GetColumnName(this PropertyInfo column) + { + var attr = column.FirstAttribute(); + return string.IsNullOrWhiteSpace(attr?.Name) ? column.Name : attr.Name; + } + + /// + /// Gets a quoted table and field name. + /// + /// The type of the DTO. + /// An . + /// An expression specifying the field. + /// An optional table alias. + /// + public static string GetFieldName(this ISqlSyntaxProvider sqlSyntax, Expression> fieldSelector, string tableAlias = null) + { + var field = ExpressionHelper.FindProperty(fieldSelector).Item1 as PropertyInfo; + var fieldName = field.GetColumnName(); + + var type = typeof(TDto); + var tableName = tableAlias ?? type.GetTableName(); + + return sqlSyntax.GetQuotedTableName(tableName) + "." + sqlSyntax.GetQuotedColumnName(fieldName); + } + } +} diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs index 8d2a230fec..ea597007b7 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs @@ -39,6 +39,7 @@ namespace Umbraco.Core.Persistence private string _providerName; private DbProviderFactory _dbProviderFactory; private DatabaseType _databaseType; + private bool _serverVersionDetected; private ISqlSyntaxProvider _sqlSyntax; private RetryPolicy _connectionRetryPolicy; private RetryPolicy _commandRetryPolicy; @@ -112,7 +113,56 @@ namespace Umbraco.Core.Persistence public bool Configured { get; private set; } /// - public bool CanConnect => Configured && DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName); + public bool CanConnect + { + get + { + if (!Configured || !DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName)) return false; + + if (_serverVersionDetected) return true; + + if (_databaseType.IsSqlServer()) + DetectSqlServerVersion(); + _serverVersionDetected = true; + + return true; + } + } + + private void DetectSqlServerVersion() + { + // replace NPoco database type by a more efficient one + + var setting = ConfigurationManager.AppSettings["Umbraco.DatabaseFactory.ServerVersion"]; + var fromSettings = false; + + if (setting.IsNullOrWhiteSpace() || !setting.StartsWith("SqlServer.") + || !Enum.TryParse(setting.Substring("SqlServer.".Length), out var versionName, true)) + { + versionName = SqlServerSyntaxProvider.GetVersionName(_connectionString, _providerName); + } + else + { + fromSettings = true; + } + + switch (versionName) + { + case SqlServerSyntaxProvider.VersionName.V2008: + _databaseType = DatabaseType.SqlServer2008; + break; + case SqlServerSyntaxProvider.VersionName.V2012: + case SqlServerSyntaxProvider.VersionName.V2014: + case SqlServerSyntaxProvider.VersionName.V2016: + case SqlServerSyntaxProvider.VersionName.V2017: + _databaseType = DatabaseType.SqlServer2012; + break; + // else leave unchanged + } + + _logger.Debug("SqlServer {SqlServerVersion}, DatabaseType is {DatabaseType} ({Source}).", + versionName, _databaseType, fromSettings ? "settings" : "detected"); + } /// public ISqlContext SqlContext => _sqlContext; diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs index e13419f0d6..e311c3bbfc 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs @@ -57,6 +57,9 @@ namespace Umbraco.Core.PropertyEditors [JsonProperty("defaultConfig")] public virtual IDictionary DefaultConfiguration => new Dictionary(); + /// + public virtual object DefaultConfigurationObject => DefaultConfiguration; + /// public virtual bool IsConfiguration(object obj) => obj is IDictionary; @@ -97,7 +100,7 @@ namespace Umbraco.Core.PropertyEditors // clone the default configuration, and apply the current configuration values var d = new Dictionary(DefaultConfiguration); - foreach ((var key, var value) in c) + foreach (var (key, value) in c) d[key] = value; return d; } diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs index 2145a580e4..cf007e681d 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs @@ -94,7 +94,10 @@ namespace Umbraco.Core.PropertyEditors } /// - public override IDictionary DefaultConfiguration => ToConfigurationEditor(new TConfiguration()); + public override IDictionary DefaultConfiguration => ToConfigurationEditor(DefaultConfigurationObject); + + /// + public override object DefaultConfigurationObject => new TConfiguration(); /// public override bool IsConfiguration(object obj) diff --git a/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs index cd8c8be030..003fe9a80e 100644 --- a/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs @@ -15,8 +15,24 @@ namespace Umbraco.Core.PropertyEditors /// /// Gets the default configuration. /// + /// + /// For basic configuration editors, this will be a dictionary of key/values. For advanced editors + /// which inherit from , this will be the dictionary + /// equivalent of an actual configuration object (ie an instance of TConfiguration, obtained + /// via . + /// IDictionary DefaultConfiguration { get; } + /// + /// Gets the default configuration object. + /// + /// + /// For basic configuration editors, this will be , ie a + /// dictionary of key/values. For advanced editors which inherit from , + /// this will be an actual configuration object (ie an instance of TConfiguration. + /// + object DefaultConfigurationObject { get; } + /// /// Determines whether a configuration object is of the type expected by the configuration editor. /// @@ -48,7 +64,7 @@ namespace Umbraco.Core.PropertyEditors IDictionary ToConfigurationEditor(object configuration); /// - /// Converts the configuration object to values for the value editror. + /// Converts the configuration object to values for the value editor. /// /// The configuration. IDictionary ToValueEditor(object configuration); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs index 38a8808856..a6ec9af01d 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs @@ -138,7 +138,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters // fixme MOVE TO MODELS O /// /// public bool HasFocalPoint() - => FocalPoint != null && FocalPoint.Left != 0.5m && FocalPoint.Top != 0.5m; + => FocalPoint != null && (FocalPoint.Left != 0.5m || FocalPoint.Top != 0.5m); /// /// Determines whether the value has a specified crop. diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 3afbb9f9ed..dfe9c5d9f9 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -369,7 +369,7 @@ namespace Umbraco.Core.Runtime _state.CurrentMigrationState = state; _state.FinalMigrationState = umbracoPlan.FinalState; - logger.Debug("Final upgrade state is '{FinalMigrationState}', database contains {DatabaseState}", _state.FinalMigrationState, state ?? ""); + logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", _state.FinalMigrationState, state ?? ""); return state == _state.FinalMigrationState; } diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index d09bd95081..0177619a68 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -117,7 +117,7 @@ namespace Umbraco.Core var change = url != null && !_applicationUrls.Contains(url); if (change) { - _logger.Info(typeof(ApplicationUrlHelper), "New url '{Url}' detected, re-discovering application url.", url); + _logger.Info(typeof(ApplicationUrlHelper), "New url {Url} detected, re-discovering application url.", url); _applicationUrls.Add(url); } diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index 3b497bff86..b65ab83439 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; +using System.Globalization; using System.Linq; using System.Security.Claims; using System.Security.Principal; @@ -63,5 +65,6 @@ namespace Umbraco.Core.Security /// Used so that we aren't creating a new CultureInfo object for every single request /// private static readonly ConcurrentDictionary UserCultures = new ConcurrentDictionary(); + } } diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index 63970aca8f..2dc14c727f 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -635,7 +635,9 @@ namespace Umbraco.Core.Security || identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value) { anythingChanged = true; - user.LastLoginDate = identityUser.LastLoginDateUtc.Value.ToLocalTime(); + //if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime + var dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime(); + user.LastLoginDate = dt; } if (identityUser.IsPropertyDirty("LastPasswordChangeDateUtc") || (user.LastPasswordChangeDate != default(DateTime) && identityUser.LastPasswordChangeDateUtc.HasValue == false) diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 0c8461c2f2..f6b2483206 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -720,7 +720,7 @@ namespace Umbraco.Core.Security } else { - //if the salt bytes is too long for the required key length for the algorithm, extend it + //if the salt bytes is too short for the required key length for the algorithm, extend it var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; var dstOffset = 0; while (dstOffset < numArray2.Length) diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 2e788382fe..022bee8b41 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -166,31 +166,28 @@ namespace Umbraco.Core.Services IEnumerable GetContentInRecycleBin(); /// - /// Gets child documents of a given parent. + /// Gets child documents of a parent. /// /// The parent identifier. /// The page number. /// The page size. /// Total number of documents. - /// A field to order by. - /// The ordering direction. /// Search text filter. + /// Ordering infos. IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + string filter = null, Ordering ordering = null); /// - /// Gets child documents of a given parent. + /// Gets child documents of a parent. /// /// The parent identifier. /// The page number. /// The page size. /// Total number of documents. - /// A field to order by. - /// The ordering direction. - /// A flag indicating whether the ordering field is a system field. /// Query filter. + /// Ordering infos. IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); + IQuery filter, Ordering ordering = null); /// /// Gets descendant documents of a given parent. diff --git a/src/Umbraco.Core/Services/ITaskService.cs b/src/Umbraco.Core/Services/ITaskService.cs deleted file mode 100644 index 2fd37777ad..0000000000 --- a/src/Umbraco.Core/Services/ITaskService.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Services -{ - public interface ITaskService : IService - { - TaskType GetTaskTypeByAlias(string taskTypeAlias); - TaskType GetTaskTypeById(int id); - void Save(TaskType taskType); - void Delete(TaskType taskTypeEntity); - - //IEnumerable GetTasks(Guid? itemId = null, int? assignedUser = null, int? ownerUser = null, string taskTypeAlias = null, bool includeClosed = false); - IEnumerable GetTasks(int? itemId = null, int? assignedUser = null, int? ownerUser = null, string taskTypeAlias = null, bool includeClosed = false); - - /// - /// Saves a task - /// - /// - void Save(Task task); - void Delete(Task task); - - IEnumerable GetAllTaskTypes(); - Task GetTaskById(int id); - } -} diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 0bb00ef5a4..a849813b13 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -553,97 +553,47 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page index (zero based) - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects + /// public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, string filter = "") + string filter = null, Ordering ordering = null) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.ContentTree); - var filterQuery = filter.IsNullOrWhiteSpace() - ? null - : Query().Where(x => x.Name.Contains(filter)); + var filterQuery = filter.IsNullOrWhiteSpace() + ? null + : Query().Where(x => x.Name.Contains(filter)); - return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filterQuery); - } + return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, filterQuery, ordering); } - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page index (zero based) - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// - /// An Enumerable list of objects + /// public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) + IQuery filter, Ordering ordering = null) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + if (ordering == null) + ordering = Ordering.By("sortOrder"); + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.ContentTree); - var query = Query(); - //if the id is System Root, then just get all - NO! does not make sense! - //if (id != Constants.System.Root) - query.Where(x => x.ParentId == id); - return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + var query = Query().Where(x => x.ParentId == id); + return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering); } } - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects + /// public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.ContentTree); - var filterQuery = filter.IsNullOrWhiteSpace() - ? null - : Query().Where(x => x.Name.Contains(filter)); + var filterQuery = filter.IsNullOrWhiteSpace() + ? null + : Query().Where(x => x.Name.Contains(filter)); - return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filterQuery); - } + return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filterQuery); } - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search filter - /// An Enumerable list of objects + /// public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); @@ -654,6 +604,7 @@ namespace Umbraco.Core.Services.Implement scope.ReadLock(Constants.Locks.ContentTree); var query = Query(); + //if the id is System Root, then just get all if (id != Constants.System.Root) { @@ -665,7 +616,8 @@ namespace Umbraco.Core.Services.Implement } query.Where(x => x.Path.SqlStartsWith($"{contentPath[0].Path},", TextColumnType.NVarchar)); } - return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + + return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } } @@ -1036,14 +988,14 @@ namespace Umbraco.Core.Services.Implement UnpublishResultType result; if (culture == "*" || culture == null) { - Audit(AuditType.UnPublish, "Unpublished by user", userId, content.Id); + Audit(AuditType.Unpublish, "Unpublished by user", userId, content.Id); result = UnpublishResultType.Success; } else { - Audit(AuditType.UnPublish, $"Culture \"{culture}\" unpublished by user", userId, content.Id); + Audit(AuditType.Unpublish, $"Culture \"{culture}\" unpublished by user", userId, content.Id); if (!content.Published) - Audit(AuditType.UnPublish, $"Unpublished (culture \"{culture}\" is mandatory) by user", userId, content.Id); + Audit(AuditType.Unpublish, $"Unpublished (culture \"{culture}\" is mandatory) by user", userId, content.Id); result = content.Published ? UnpublishResultType.SuccessCulture : UnpublishResultType.SuccessMandatoryCulture; } scope.Complete(); @@ -1082,7 +1034,7 @@ namespace Umbraco.Core.Services.Implement var cannotBePublished = publishedCultures.Count == 0; // no published cultures = cannot be published if (!cannotBePublished) { - var mandatoryCultures = _languageRepository.GetMany().Where(x => x.Mandatory).Select(x => x.IsoCode); + var mandatoryCultures = _languageRepository.GetMany().Where(x => x.IsMandatory).Select(x => x.IsoCode); cannotBePublished = mandatoryCultures.Any(x => !publishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase)); // missing mandatory culture = cannot be published } @@ -1168,9 +1120,9 @@ namespace Umbraco.Core.Services.Implement if (unpublishResult.Success) // and succeeded, trigger events { // events and audit - scope.Events.Dispatch(UnPublished, this, new PublishEventArgs(content, false, false), "UnPublished"); + scope.Events.Dispatch(Unpublished, this, new PublishEventArgs(content, false, false), "Unpublished"); scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.RefreshBranch).ToEventArgs()); - Audit(AuditType.UnPublish, "Unpublished by user", userId, content.Id); + Audit(AuditType.Unpublish, "Unpublished by user", userId, content.Id); scope.Complete(); return new PublishResult(PublishResultType.Success, evtMsgs, content); } @@ -1396,10 +1348,10 @@ namespace Umbraco.Core.Services.Implement scope.WriteLock(Constants.Locks.ContentTree); // if it's not trashed yet, and published, we should unpublish - // but... UnPublishing event makes no sense (not going to cancel?) and no need to save + // but... Unpublishing event makes no sense (not going to cancel?) and no need to save // just raise the event if (content.Trashed == false && content.Published) - scope.Events.Dispatch(UnPublished, this, new PublishEventArgs(content, false, false), nameof(UnPublished)); + scope.Events.Dispatch(Unpublished, this, new PublishEventArgs(content, false, false), nameof(Unpublished)); DeleteLocked(scope, content); @@ -2146,12 +2098,12 @@ namespace Umbraco.Core.Services.Implement /// /// Occurs before unpublish /// - public static event TypedEventHandler> UnPublishing; + public static event TypedEventHandler> Unpublishing; /// /// Occurs after unpublish /// - public static event TypedEventHandler> UnPublished; + public static event TypedEventHandler> Unpublished; /// /// Occurs after change. @@ -2178,7 +2130,7 @@ namespace Umbraco.Core.Services.Implement // raise Publishing event if (scope.Events.DispatchCancelable(Publishing, this, new PublishEventArgs(content, evtMsgs))) { - Logger.Info("Document '{ContentName}' (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled"); + Logger.Info("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled"); return new PublishResult(PublishResultType.FailedCancelledByEvent, evtMsgs, content); } @@ -2186,7 +2138,7 @@ namespace Umbraco.Core.Services.Implement // either because it is 'publishing' or because it already has a published version if (((Content) content).PublishedState != PublishedState.Publishing && content.PublishedVersionId == 0) { - Logger.Info("Document '{ContentName}' (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document does not have published values"); + Logger.Info("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document does not have published values"); return new PublishResult(PublishResultType.FailedNoPublishedValues, evtMsgs, content); } @@ -2194,15 +2146,15 @@ namespace Umbraco.Core.Services.Implement switch (content.Status) { case ContentStatus.Expired: - Logger.Info("Document '{ContentName}' (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document has expired"); + Logger.Info("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document has expired"); return new PublishResult(PublishResultType.FailedHasExpired, evtMsgs, content); case ContentStatus.AwaitingRelease: - Logger.Info("Document '{ContentName}' (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document is awaiting release"); + Logger.Info("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document is awaiting release"); return new PublishResult(PublishResultType.FailedAwaitingRelease, evtMsgs, content); case ContentStatus.Trashed: - Logger.Info("Document '{ContentName}' (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document is trashed"); + Logger.Info("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document is trashed"); return new PublishResult(PublishResultType.FailedIsTrashed, evtMsgs, content); } @@ -2214,7 +2166,7 @@ namespace Umbraco.Core.Services.Implement var pathIsOk = content.ParentId == Constants.System.Root || IsPathPublished(GetParent(content)); if (pathIsOk == false) { - Logger.Info("Document '{ContentName}' (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "parent is not published"); + Logger.Info("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "parent is not published"); return new PublishResult(PublishResultType.FailedPathNotPublished, evtMsgs, content); } @@ -2238,17 +2190,17 @@ namespace Umbraco.Core.Services.Implement // change state to publishing ((Content) content).PublishedState = PublishedState.Publishing; - Logger.Info("Document '{ContentName}' (id={ContentId}) has been published.", content.Name, content.Id); + Logger.Info("Document {ContentName} (id={ContentId}) has been published.", content.Name, content.Id); return result; } // ensures that a document can be unpublished internal UnpublishResult StrategyCanUnpublish(IScope scope, IContent content, int userId, EventMessages evtMsgs) { - // raise UnPublishing event - if (scope.Events.DispatchCancelable(UnPublishing, this, new PublishEventArgs(content, evtMsgs))) + // raise Unpublishing event + if (scope.Events.DispatchCancelable(Unpublishing, this, new PublishEventArgs(content, evtMsgs))) { - Logger.Info("Document '{ContentName}' (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", content.Name, content.Id); + Logger.Info("Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", content.Name, content.Id); return new UnpublishResult(UnpublishResultType.FailedCancelledByEvent, evtMsgs, content); } @@ -2271,13 +2223,13 @@ namespace Umbraco.Core.Services.Implement if (content.ReleaseDate.HasValue && content.ReleaseDate.Value <= DateTime.Now) { content.ReleaseDate = null; - Logger.Info("Document '{ContentName}' (id={ContentId}) had its release date removed, because it was unpublished.", content.Name, content.Id); + Logger.Info("Document {ContentName} (id={ContentId}) had its release date removed, because it was unpublished.", content.Name, content.Id); } // change state to unpublishing ((Content) content).PublishedState = PublishedState.Unpublishing; - Logger.Info("Document '{ContentName}' (id={ContentId}) has been unpublished.", content.Name, content.Id); + Logger.Info("Document {ContentName} (id={ContentId}) has been unpublished.", content.Name, content.Id); return attempt; } @@ -2330,10 +2282,10 @@ namespace Umbraco.Core.Services.Implement foreach (var content in contents.OrderByDescending(x => x.ParentId)) { // if it's not trashed yet, and published, we should unpublish - // but... UnPublishing event makes no sense (not going to cancel?) and no need to save + // but... Unpublishing event makes no sense (not going to cancel?) and no need to save // just raise the event if (content.Trashed == false && content.Published) - scope.Events.Dispatch(UnPublished, this, new PublishEventArgs(content, false, false), nameof(UnPublished)); + scope.Events.Dispatch(Unpublished, this, new PublishEventArgs(content, false, false), nameof(Unpublished)); // if current content has children, move them to trash var c = content; diff --git a/src/Umbraco.Core/Services/Implement/LocalizationService.cs b/src/Umbraco.Core/Services/Implement/LocalizationService.cs index 663ecf586c..49a764b533 100644 --- a/src/Umbraco.Core/Services/Implement/LocalizationService.cs +++ b/src/Umbraco.Core/Services/Implement/LocalizationService.cs @@ -360,6 +360,19 @@ namespace Umbraco.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope()) { + // write-lock languages to guard against race conds when dealing with default language + scope.WriteLock(Constants.Locks.Languages); + + // look for cycles - within write-lock + if (language.FallbackLanguageId.HasValue) + { + var languages = _languageRepository.GetMany().ToDictionary(x => x.Id, x => x); + if (!languages.ContainsKey(language.FallbackLanguageId.Value)) + throw new InvalidOperationException($"Cannot save language {language.IsoCode} with fallback id={language.FallbackLanguageId.Value} which is not a valid language id."); + if (CreatesCycle(language, languages)) + throw new InvalidOperationException($"Cannot save language {language.IsoCode} with fallback {languages[language.FallbackLanguageId.Value].IsoCode} as it would create a fallback cycle."); + } + var saveEventArgs = new SaveEventArgs(language); if (scope.Events.DispatchCancelable(SavingLanguage, this, saveEventArgs)) { @@ -377,6 +390,20 @@ namespace Umbraco.Core.Services.Implement } } + private bool CreatesCycle(ILanguage language, IDictionary languages) + { + // a new language is not referenced yet, so cannot be part of a cycle + if (!language.HasIdentity) return false; + + var id = language.FallbackLanguageId; + while (true) // assuming languages does not already contains a cycle, this must end + { + if (!id.HasValue) return false; // no fallback means no cycle + if (id.Value == language.Id) return true; // back to language = cycle! + id = languages[id.Value].FallbackLanguageId; // else keep chaining + } + } + /// /// Deletes a by removing it (but not its usages) from the db /// @@ -386,6 +413,9 @@ namespace Umbraco.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope()) { + // write-lock languages to guard against race conds when dealing with default language + scope.WriteLock(Constants.Locks.Languages); + var deleteEventArgs = new DeleteEventArgs(language); if (scope.Events.DispatchCancelable(DeletingLanguage, this, deleteEventArgs)) { @@ -393,8 +423,7 @@ namespace Umbraco.Core.Services.Implement return; } - //NOTE: There isn't any constraints in the db, so possible references aren't deleted - + // NOTE: Other than the fall-back language, there aren't any other constraints in the db, so possible references aren't deleted _languageRepository.Delete(language); deleteEventArgs.CanCancel = false; diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index 8f6a1c6000..431e20044c 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -515,14 +515,9 @@ namespace Umbraco.Core.Services.Implement using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.MediaTree); - var query = Query(); - //if the id is System Root, then just get all - NO! does not make sense! - //if (id != Constants.System.Root) - - query.Where(x => x.ParentId == id); - - return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + var query = Query().Where(x => x.ParentId == id); + return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } } @@ -561,7 +556,7 @@ namespace Umbraco.Core.Services.Implement var filterQuery = filter.IsNullOrWhiteSpace() ? null : Query().Where(x => x.Name.Contains(filter)); - return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); + return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filterQuery, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } } @@ -607,6 +602,7 @@ namespace Umbraco.Core.Services.Implement scope.ReadLock(Constants.Locks.MediaTree); var query = Query(); + //if the id is System Root, then just get all if (id != Constants.System.Root) { @@ -618,7 +614,8 @@ namespace Umbraco.Core.Services.Implement } query.Where(x => x.Path.SqlStartsWith(mediaPath[0].Path + ",", TextColumnType.NVarchar)); } - return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + + return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } } diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index 288809bf33..211e30d01c 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -387,7 +387,7 @@ namespace Umbraco.Core.Services.Implement using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.MemberTree); - return _memberRepository.GetPage(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, true); + return _memberRepository.GetPage(null, pageIndex, pageSize, out totalRecords, null, Ordering.By("LoginName")); } } @@ -405,7 +405,7 @@ namespace Umbraco.Core.Services.Implement scope.ReadLock(Constants.Locks.MemberTree); var query1 = memberTypeAlias == null ? null : Query().Where(x => x.ContentTypeAlias == memberTypeAlias); var query2 = filter == null ? null : Query().Where(x => x.Name.Contains(filter) || x.Username.Contains(filter) || x.Email.Contains(filter)); - return _memberRepository.GetPage(query1, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, query2); + return _memberRepository.GetPage(query1, pageIndex, pageSize, out totalRecords, query2, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } } @@ -557,7 +557,7 @@ namespace Umbraco.Core.Services.Implement throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback // causes rollback } - return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending, true); + return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("Name")); } } @@ -598,7 +598,7 @@ namespace Umbraco.Core.Services.Implement throw new ArgumentOutOfRangeException(nameof(matchType)); } - return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending, true); + return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("Email")); } } @@ -639,7 +639,7 @@ namespace Umbraco.Core.Services.Implement throw new ArgumentOutOfRangeException(nameof(matchType)); } - return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, true); + return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("LoginName")); } } diff --git a/src/Umbraco.Core/Services/Implement/PackagingService.cs b/src/Umbraco.Core/Services/Implement/PackagingService.cs index ef22861947..67e07e63b6 100644 --- a/src/Umbraco.Core/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Core/Services/Implement/PackagingService.cs @@ -52,6 +52,7 @@ namespace Umbraco.Core.Services.Implement private readonly IAuditRepository _auditRepository; private readonly IContentTypeRepository _contentTypeRepository; private readonly PropertyEditorCollection _propertyEditors; + private static HttpClient _httpClient; public PackagingService( ILogger logger, @@ -1441,7 +1442,6 @@ namespace Umbraco.Core.Services.Implement /// public string FetchPackageFile(Guid packageId, Version umbracoVersion, int userId) { - using (var httpClient = new HttpClient()) using (var scope = _scopeProvider.CreateScope()) { //includeHidden = true because we don't care if it's hidden we want to get the file regardless @@ -1449,7 +1449,11 @@ namespace Umbraco.Core.Services.Implement byte[] bytes; try { - bytes = httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult(); + if (_httpClient == null) + { + _httpClient = new HttpClient(); + } + bytes = _httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult(); } catch (HttpRequestException ex) { diff --git a/src/Umbraco.Core/Services/Implement/ServerRegistrationService.cs b/src/Umbraco.Core/Services/Implement/ServerRegistrationService.cs index 7d5eeefd78..d9ce978274 100644 --- a/src/Umbraco.Core/Services/Implement/ServerRegistrationService.cs +++ b/src/Umbraco.Core/Services/Implement/ServerRegistrationService.cs @@ -76,13 +76,13 @@ namespace Umbraco.Core.Services.Implement // reload - cheap, cached // default role is single server, but if registrations contain more - // than one active server, then role is master or slave + // than one active server, then role is master or replica regs = _serverRegistrationRepository.GetMany().ToArray(); // default role is single server, but if registrations contain more - // than one active server, then role is master or slave + // than one active server, then role is master or replica _currentServerRole = regs.Count(x => x.IsActive) > 1 - ? (server.IsMaster ? ServerRole.Master : ServerRole.Slave) + ? (server.IsMaster ? ServerRole.Master : ServerRole.Replica) : ServerRole.Single; scope.Complete(); diff --git a/src/Umbraco.Core/Services/Implement/TaskService.cs b/src/Umbraco.Core/Services/Implement/TaskService.cs deleted file mode 100644 index 74fdddedfa..0000000000 --- a/src/Umbraco.Core/Services/Implement/TaskService.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Scoping; - -namespace Umbraco.Core.Services.Implement -{ - public class TaskService : RepositoryService, ITaskService - { - private readonly ITaskTypeRepository _taskTypeRepository; - private readonly ITaskRepository _taskRepository; - - public TaskService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, - ITaskTypeRepository taskTypeRepository, ITaskRepository taskRepository) - : base(provider, logger, eventMessagesFactory) - { - _taskTypeRepository = taskTypeRepository; - _taskRepository = taskRepository; - } - - public TaskType GetTaskTypeByAlias(string taskTypeAlias) - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - return _taskTypeRepository.Get(Query().Where(x => x.Alias == taskTypeAlias)).FirstOrDefault(); - } - } - - public TaskType GetTaskTypeById(int id) - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - return _taskTypeRepository.Get(id); - } - } - - public void Save(TaskType taskType) - { - using (var scope = ScopeProvider.CreateScope()) - { - _taskTypeRepository.Save(taskType); - scope.Complete(); - } - } - - public void Delete(TaskType taskTypeEntity) - { - using (var scope = ScopeProvider.CreateScope()) - { - _taskTypeRepository.Delete(taskTypeEntity); - scope.Complete(); - } - } - - public IEnumerable GetAllTaskTypes() - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - return _taskTypeRepository.GetMany(); - } - } - - - public IEnumerable GetTasks(int? itemId = null, int? assignedUser = null, int? ownerUser = null, string taskTypeAlias = null, bool includeClosed = false) - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - return _taskRepository.GetTasks(itemId, assignedUser, ownerUser, taskTypeAlias); - } - } - - /// - /// Saves a task - /// - /// - public void Save(Task task) - { - using (var scope = ScopeProvider.CreateScope()) - { - _taskRepository.Save(task); - scope.Complete(); - } - } - - public void Delete(Task task) - { - using (var scope = ScopeProvider.CreateScope()) - { - _taskRepository.Delete(task); - scope.Complete(); - } - } - - public Task GetTaskById(int id) - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - return _taskRepository.Get(id); - } - } - } -} diff --git a/src/Umbraco.Core/Services/Implement/UserService.cs b/src/Umbraco.Core/Services/Implement/UserService.cs index 96cac1d814..44358caa84 100644 --- a/src/Umbraco.Core/Services/Implement/UserService.cs +++ b/src/Umbraco.Core/Services/Implement/UserService.cs @@ -645,7 +645,7 @@ namespace Umbraco.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - return _userRepository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, member => member.Username); + return _userRepository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, member => member.Name); } } diff --git a/src/Umbraco.Core/Services/OperationResult.cs b/src/Umbraco.Core/Services/OperationResult.cs index d22fd0147f..e901b58119 100644 --- a/src/Umbraco.Core/Services/OperationResult.cs +++ b/src/Umbraco.Core/Services/OperationResult.cs @@ -11,6 +11,10 @@ namespace Umbraco.Core.Services /// Represents the result of a service operation. /// /// The type of the result type. + /// Type must be an enumeration, and its + /// underlying type must be byte. Values indicating success should be in the 0-127 + /// range, while values indicating failure should be in the 128-255 range. See + /// for a base implementation. public class OperationResult where TResultType : struct { @@ -56,6 +60,10 @@ namespace Umbraco.Core.Services /// /// The type of the result type. /// The type of the entity. + /// Type must be an enumeration, and its + /// underlying type must be byte. Values indicating success should be in the 0-127 + /// range, while values indicating failure should be in the 128-255 range. See + /// for a base implementation. public class OperationResult : OperationResult where TResultType : struct { @@ -111,7 +119,8 @@ namespace Umbraco.Core.Services return new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages); } - // fixme wtf? + // fixme - this exists to support services that still return Attempt + // these services should directly return an OperationResult, and then this static class should be deleted internal static class Attempt { /// diff --git a/src/Umbraco.Core/Services/Ordering.cs b/src/Umbraco.Core/Services/Ordering.cs new file mode 100644 index 0000000000..9713aa6bf5 --- /dev/null +++ b/src/Umbraco.Core/Services/Ordering.cs @@ -0,0 +1,81 @@ +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Services +{ + /// + /// Represents ordering information. + /// + public class Ordering + { + private static readonly Ordering DefaultOrdering = new Ordering(null); + + /// + /// Initializes a new instance of the class. + /// + /// The name of the ordering field. + /// The ordering direction. + /// The (ISO) culture to consider when sorting multi-lingual fields. + /// A value indicating whether the ordering field is a custom user property. + /// + /// The can be null, meaning: not sorting. If it is the empty string, it becomes null. + /// The can be the empty string, meaning: invariant. If it is null, it becomes the empty string. + /// + public Ordering(string orderBy, Direction direction = Direction.Ascending, string culture = null, bool isCustomField = false) + { + OrderBy = orderBy.IfNullOrWhiteSpace(null); // empty is null and means, not sorting + Direction = direction; + Culture = culture.IfNullOrWhiteSpace(string.Empty); // empty is "" and means, invariant + IsCustomField = isCustomField; + } + + /// + /// Creates a new instance of the class. + /// + /// The name of the ordering field. + /// The ordering direction. + /// The (ISO) culture to consider when sorting multi-lingual fields. + /// A value indicating whether the ordering field is a custom user property. + /// + /// The can be null, meaning: not sorting. If it is the empty string, it becomes null. + /// The can be the empty string, meaning: invariant. If it is null, it becomes the empty string. + /// + public static Ordering By(string orderBy, Direction direction = Direction.Ascending, string culture = null, bool isCustomField = false) + => new Ordering(orderBy, direction, culture, isCustomField); + + /// + /// Gets the default instance. + /// + public static Ordering ByDefault() + => DefaultOrdering; + + /// + /// Gets the name of the ordering field. + /// + public string OrderBy { get; } + + /// + /// Gets the ordering direction. + /// + public Direction Direction { get; } + + /// + /// Gets (ISO) culture to consider when sorting multi-lingual fields. + /// + public string Culture { get; } + + /// + /// Gets a value indicating whether the ordering field is a custom user property. + /// + public bool IsCustomField { get; } + + /// + /// Gets a value indicating whether this ordering is the default ordering. + /// + public bool IsEmpty => this == DefaultOrdering || OrderBy == null; + + /// + /// Gets a value indicating whether the culture of this ordering is invariant. + /// + public bool IsInvariant => this == DefaultOrdering || Culture == string.Empty; + } +} diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index b14158efd5..e6df6dec81 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -8,7 +8,6 @@ namespace Umbraco.Core.Services public class ServiceContext { private readonly Lazy _publicAccessService; - private readonly Lazy _taskService; private readonly Lazy _domainService; private readonly Lazy _auditService; private readonly Lazy _localizedTextService; @@ -40,10 +39,9 @@ namespace Umbraco.Core.Services /// Initializes a new instance of the class with lazy services. /// /// Used by IoC. Note that LightInject will favor lazy args when picking a constructor. - public ServiceContext(Lazy publicAccessService, Lazy taskService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy treeService, Lazy sectionService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService, Lazy redirectUrlService, Lazy consentService) + public ServiceContext(Lazy publicAccessService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy treeService, Lazy sectionService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService, Lazy redirectUrlService, Lazy consentService) { _publicAccessService = publicAccessService; - _taskService = taskService; _domainService = domainService; _auditService = auditService; _localizedTextService = localizedTextService; @@ -97,7 +95,6 @@ namespace Umbraco.Core.Services ILocalizedTextService localizedTextService = null, IAuditService auditService = null, IDomainService domainService = null, - ITaskService taskService = null, IMacroService macroService = null, IPublicAccessService publicAccessService = null, IExternalLoginService externalLoginService = null, @@ -128,7 +125,6 @@ namespace Umbraco.Core.Services if (userService != null) _userService = new Lazy(() => userService); if (notificationService != null) _notificationService = new Lazy(() => notificationService); if (domainService != null) _domainService = new Lazy(() => domainService); - if (taskService != null) _taskService = new Lazy(() => taskService); if (macroService != null) _macroService = new Lazy(() => macroService); if (publicAccessService != null) _publicAccessService = new Lazy(() => publicAccessService); if (redirectUrlService != null) _redirectUrlService = new Lazy(() => redirectUrlService); @@ -139,12 +135,7 @@ namespace Umbraco.Core.Services /// Gets the /// public IPublicAccessService PublicAccessService => _publicAccessService.Value; - - /// - /// Gets the - /// - public ITaskService TaskService => _taskService.Value; - + /// /// Gets the /// diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index aa7075f418..9c686c4353 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -1384,6 +1384,30 @@ namespace Umbraco.Core return idCheckList.Contains(value); } + /// + /// Converts a file name to a friendly name for a content item + /// + /// + /// + public static string ToFriendlyName(this string fileName) + { + // strip the file extension + fileName = fileName.StripFileExtension(); + + // underscores and dashes to spaces + fileName = fileName.ReplaceMany(new[] { '_', '-' }, ' '); + + // any other conversions ? + + // Pascalcase (to be done last) + fileName = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(fileName); + + // Replace multiple consecutive spaces with a single space + fileName = string.Join(" ", fileName.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); + + return fileName; + } + // From: http://stackoverflow.com/a/961504/5018 // filters control characters but allows only properly-formed surrogate sequences private static readonly Lazy InvalidXmlChars = new Lazy(() => diff --git a/src/Umbraco.Core/Sync/ServerRole.cs b/src/Umbraco.Core/Sync/ServerRole.cs index a42f015c5b..21091e41c5 100644 --- a/src/Umbraco.Core/Sync/ServerRole.cs +++ b/src/Umbraco.Core/Sync/ServerRole.cs @@ -16,9 +16,9 @@ Single = 1, /// - /// In a multi-servers environment, the server is a slave server. + /// In a multi-servers environment, the server is a replica server. /// - Slave = 2, + Replica = 2, /// /// In a multi-servers environment, the server is the master server. diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 00ef58e0a2..dcf6832b6c 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -38,8 +38,6 @@ - - @@ -58,21 +56,22 @@ - - - - - - - - + + 2.2.2 + + + 5.2.6 + + + 4.0.0 + - + 2.7.1 @@ -110,8 +109,7 @@ - - + @@ -200,7 +198,6 @@ - @@ -336,10 +333,16 @@ + + + + - + + + @@ -360,16 +363,22 @@ + + + + + + @@ -391,12 +400,14 @@ + + @@ -413,6 +424,8 @@ + + @@ -576,7 +589,6 @@ - @@ -591,49 +603,17 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -650,7 +630,7 @@ - + @@ -758,7 +738,6 @@ - @@ -832,8 +811,6 @@ - - @@ -855,8 +832,6 @@ - - @@ -931,8 +906,6 @@ - - @@ -982,7 +955,6 @@ - @@ -1191,8 +1163,6 @@ - - @@ -1223,8 +1193,6 @@ - - @@ -1315,19 +1283,10 @@ - - - - - - - - - @@ -1338,8 +1297,6 @@ - - @@ -1421,7 +1378,6 @@ - @@ -1440,6 +1396,7 @@ + @@ -1450,15 +1407,14 @@ - - - - + + + @@ -1540,6 +1496,8 @@ - + + + \ No newline at end of file diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index d018dddfbf..b99fc2695e 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -30,6 +30,7 @@ namespace Umbraco.Core /// These are def back office: /// /Umbraco/RestServices = back office /// /Umbraco/BackOffice = back office + /// /Umbraco/Preview = back office /// If it's not any of the above, and there's no extension then we cannot determine if it's back office or front-end /// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice /// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute. @@ -77,7 +78,8 @@ namespace Umbraco.Core //check for special back office paths if (urlPath.InvariantStartsWith("/" + globalSettings.GetUmbracoMvcArea() + "/BackOffice/") - || urlPath.InvariantStartsWith("/" + globalSettings.GetUmbracoMvcArea() + "/RestServices/")) + || urlPath.InvariantStartsWith("/" + globalSettings.GetUmbracoMvcArea() + "/RestServices/") + || urlPath.InvariantStartsWith("/" + globalSettings.GetUmbracoMvcArea() + "/Preview/")) { return true; } diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index 1771ca4d2b..678ae124d2 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -50,7 +50,7 @@ - + diff --git a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index 7c8968d93b..9755e4f9db 100644 --- a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -62,18 +62,16 @@ - - + - @@ -87,7 +85,6 @@ - diff --git a/src/Umbraco.Tests.Benchmarks/app.config b/src/Umbraco.Tests.Benchmarks/app.config index 227af0f591..b5e577b22c 100644 --- a/src/Umbraco.Tests.Benchmarks/app.config +++ b/src/Umbraco.Tests.Benchmarks/app.config @@ -7,11 +7,11 @@ - + - + @@ -55,7 +55,7 @@ - + @@ -167,7 +167,7 @@ - + @@ -267,7 +267,7 @@ - + diff --git a/src/Umbraco.Tests/Cache/CacheRefresherComponentTests.cs b/src/Umbraco.Tests/Cache/CacheRefresherComponentTests.cs index e79b26cd1d..0616b4098f 100644 --- a/src/Umbraco.Tests/Cache/CacheRefresherComponentTests.cs +++ b/src/Umbraco.Tests/Cache/CacheRefresherComponentTests.cs @@ -99,7 +99,7 @@ namespace Umbraco.Tests.Cache new EventDefinition>(null, serviceContext.ContentService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Trashed"), new EventDefinition(null, serviceContext.ContentService, new RecycleBinEventArgs(Guid.NewGuid())), new EventDefinition>(null, serviceContext.ContentService, new PublishEventArgs(Enumerable.Empty()), "Published"), - new EventDefinition>(null, serviceContext.ContentService, new PublishEventArgs(Enumerable.Empty()), "UnPublished"), + new EventDefinition>(null, serviceContext.ContentService, new PublishEventArgs(Enumerable.Empty()), "Unpublished"), new EventDefinition>(null, serviceContext.PublicAccessService, new SaveEventArgs(Enumerable.Empty())), new EventDefinition>(null, serviceContext.PublicAccessService, new DeleteEventArgs(Enumerable.Empty())), diff --git a/src/Umbraco.Tests/Composing/TypeFinderTests.cs b/src/Umbraco.Tests/Composing/TypeFinderTests.cs index 955f6f94c8..a8624e8871 100644 --- a/src/Umbraco.Tests/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeFinderTests.cs @@ -90,7 +90,7 @@ namespace Umbraco.Tests.Composing Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree] typesFound = TypeFinder.FindClassesWithAttribute(new[] { typeof (UmbracoContext).Assembly }); - Assert.AreEqual(21, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] + Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] } private static ProfilingLogger GetTestProfilingLogger() diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index c6365ba4a2..d7f2e7dd53 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -258,7 +258,7 @@ AnotherContentFinder public void Resolves_Assigned_Mappers() { var foundTypes1 = _typeLoader.GetAssignedMapperTypes(); - Assert.AreEqual(30, foundTypes1.Count()); + Assert.AreEqual(29, foundTypes1.Count()); } [Test] @@ -279,7 +279,7 @@ AnotherContentFinder public void Resolves_Trees() { var trees = _typeLoader.GetTrees(); - Assert.AreEqual(3, trees.Count()); + Assert.AreEqual(1, trees.Count()); } [Test] diff --git a/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config b/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config index 37f2723c81..4040412603 100644 --- a/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config +++ b/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config @@ -1,114 +1,114 @@  -
- - settings - - - - views/dashboard/settings/settingsdashboardintro.html - - - views/dashboard/settings/settingsdashboardvideos.html - - -
+
+ + settings + + + + views/dashboard/settings/settingsdashboardintro.html + + + views/dashboard/settings/settingsdashboardvideos.html + + + + dashboard/ExamineManagement.ascx + +
-
- - developer - - - - views/dashboard/developer/developerdashboardintro.html - - - views/dashboard/developer/developerdashboardvideos.html - - - - dashboard/ExamineManagement.ascx - -
+
+ + developer + + + + views/dashboard/developer/developerdashboardintro.html + + + views/dashboard/developer/developerdashboardvideos.html + + +
-
- - media - - - - views/dashboard/media/mediafolderbrowser.html - - - - - admin - - - views/dashboard/media/mediadashboardintro.html - - - views/dashboard/media/desktopmediauploader.html - - - views/dashboard/media/mediadashboardvideos.html - - -
+
+ + media + + + + views/dashboard/media/mediafolderbrowser.html + + + + + admin + + + views/dashboard/media/mediadashboardintro.html + + + views/dashboard/media/desktopmediauploader.html + + + views/dashboard/media/mediadashboardvideos.html + + +
-
- - translator - hello - world - - - content - - - - admin - - - views/dashboard/default/startupdashboardintro.html - - - views/dashboard/default/startupdashboardkits.html +
- editor - writer + translator + hello + world - - - views/dashboard/default/startupdashboardvideos.html - - - - dashboard/latestEdits.ascx - - - - views/dashboard/changepassword.html - - -
+ + content + + + + admin + + + views/dashboard/default/startupdashboardintro.html + + + views/dashboard/default/startupdashboardkits.html + + editor + writer + + + + views/dashboard/default/startupdashboardvideos.html + + + + dashboard/latestEdits.ascx + + + + views/dashboard/changepassword.html + + +
-
- - default - member - - - - views/dashboard/members/membersdashboardintro.html - - - members/membersearch.ascx - - - views/dashboard/members/membersdashboardvideos.html - - -
+
+ + default + member + + + + views/dashboard/members/membersdashboardintro.html + + + members/membersearch.ascx + + + views/dashboard/members/membersdashboardvideos.html + + +
diff --git a/src/Umbraco.Tests/Configurations/DashboardSettings/DashboardSettingsTests.cs b/src/Umbraco.Tests/Configurations/DashboardSettings/DashboardSettingsTests.cs index ed3b36c91f..862dfb3dc2 100644 --- a/src/Umbraco.Tests/Configurations/DashboardSettings/DashboardSettingsTests.cs +++ b/src/Umbraco.Tests/Configurations/DashboardSettings/DashboardSettingsTests.cs @@ -66,10 +66,21 @@ namespace Umbraco.Tests.Configurations.DashboardSettings [Test] public void Test_Section_Tabs() { - Assert.AreEqual(1, SettingsSection.Sections.ElementAt(0).Tabs.Count()); - Assert.AreEqual(2, SettingsSection.Sections.ElementAt(1).Tabs.Count()); + //Element 0 Alias "StartupSettingsDashboardSection" + Assert.AreEqual(2, SettingsSection.Sections.ElementAt(0).Tabs.Count()); + + //Element 1 Alias "StartupDeveloperDashboardSection" + Assert.AreEqual(1, SettingsSection.Sections.ElementAt(1).Tabs.Count()); + + //Element 2 Alias "StartupMediaDashboardSection" + Assert.AreEqual(2, SettingsSection.Sections.ElementAt(2).Tabs.Count()); + + //Element 3 Alias "StartupDashboardSection" Assert.AreEqual(3, SettingsSection.Sections.ElementAt(3).Tabs.Count()); + //Element 4 Alias "StartupMemberDashboardSection" + Assert.AreEqual(1, SettingsSection.Sections.ElementAt(4).Tabs.Count()); + } [Test] diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs index d06a7aa954..6ff2dc8340 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs @@ -23,6 +23,12 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings Assert.IsTrue(SettingsSection.WebRouting.DisableAlternativeTemplates == false); } + [Test] + public void ValidateAlternativeTemplates() + { + Assert.IsTrue(SettingsSection.WebRouting.ValidateAlternativeTemplates == false); + } + [Test] public void DisableFindContentByIdPath() { diff --git a/src/Umbraco.Tests/CoreThings/EnumerableExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/EnumerableExtensionsTests.cs index 7d7dcaea71..e734713c76 100644 --- a/src/Umbraco.Tests/CoreThings/EnumerableExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreThings/EnumerableExtensionsTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Umbraco.Core; @@ -38,7 +39,7 @@ namespace Umbraco.Tests.CoreThings } [Test] - public void Flatten_List_2() + public void SelectRecursive_2() { var hierarchy = new TestItem("1") { @@ -50,19 +51,13 @@ namespace Umbraco.Tests.CoreThings } }; -#pragma warning disable CS0618 // Type or member is obsolete - var flattened = hierarchy.Children.FlattenList(x => x.Children); -#pragma warning restore CS0618 // Type or member is obsolete var selectRecursive = hierarchy.Children.SelectRecursive(x => x.Children); - Assert.AreEqual(3, flattened.Count()); Assert.AreEqual(3, selectRecursive.Count()); - - Assert.IsTrue(flattened.SequenceEqual(selectRecursive)); } [Test] - public void Flatten_List() + public void SelectRecursive() { var hierarchy = new TestItem("1") { @@ -117,17 +112,8 @@ namespace Umbraco.Tests.CoreThings } }; -#pragma warning disable CS0618 // Type or member is obsolete - var flattened = hierarchy.Children.FlattenList(x => x.Children); -#pragma warning restore CS0618 // Type or member is obsolete var selectRecursive = hierarchy.Children.SelectRecursive(x => x.Children); - - Assert.AreEqual(10, flattened.Count()); Assert.AreEqual(10, selectRecursive.Count()); - - // both methods return the same elements, but not in the same order - Assert.IsFalse(flattened.SequenceEqual(selectRecursive)); - foreach (var x in flattened) Assert.IsTrue(selectRecursive.Contains(x)); } private class TestItem diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs index 6075ab3371..c3d3ca3f4b 100644 --- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs +++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs @@ -460,7 +460,7 @@ namespace Umbraco.Tests.Integration #endregion - #region Save, Publish & UnPublish single content + #region Save, Publish & Unpublish single content [Test] public void SaveUnpublishedContent() @@ -718,7 +718,7 @@ namespace Umbraco.Tests.Integration #endregion - #region Publish & UnPublish branch + #region Publish & Unpublish branch [Test] public void UnpublishContentBranch() diff --git a/src/Umbraco.Tests/Macros/MacroParserTests.cs b/src/Umbraco.Tests/Macros/MacroParserTests.cs index 8c03e16f3b..898ae4b20e 100644 --- a/src/Umbraco.Tests/Macros/MacroParserTests.cs +++ b/src/Umbraco.Tests/Macros/MacroParserTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using NUnit.Framework; using Umbraco.Core.Macros; +using Umbraco.Web.Macros; namespace Umbraco.Tests.Macros { diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index a399e75cff..98ebeb9315 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.Validators; using Umbraco.Core.Services; +using Umbraco.Web.ContentApps; namespace Umbraco.Tests.Manifest { @@ -343,5 +344,42 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 // fixme - should we resolveUrl in configs? } + + [Test] + public void CanParseManifest_ContentApps() + { + const string json = @"{'contentApps': [ + { + alias: 'myPackageApp1', + name: 'My App1', + icon: 'icon-foo', + view: '~/App_Plugins/MyPackage/ContentApps/MyApp1.html' + }, + { + alias: 'myPackageApp2', + name: 'My App2', + config: { key1: 'some config val' }, + icon: 'icon-bar', + view: '~/App_Plugins/MyPackage/ContentApps/MyApp2.html' + } +]}"; + + var manifest = _parser.ParseManifest(json); + Assert.AreEqual(2, manifest.ContentApps.Length); + + Assert.IsInstanceOf(manifest.ContentApps[0]); + var app0 = (ManifestContentAppDefinition) manifest.ContentApps[0]; + Assert.AreEqual("myPackageApp1", app0.Alias); + Assert.AreEqual("My App1", app0.Name); + Assert.AreEqual("icon-foo", app0.Icon); + Assert.AreEqual("/App_Plugins/MyPackage/ContentApps/MyApp1.html", app0.View); + + Assert.IsInstanceOf(manifest.ContentApps[1]); + var app1 = (ManifestContentAppDefinition)manifest.ContentApps[1]; + Assert.AreEqual("myPackageApp2", app1.Alias); + Assert.AreEqual("My App2", app1.Name); + Assert.AreEqual("icon-bar", app1.Icon); + Assert.AreEqual("/App_Plugins/MyPackage/ContentApps/MyApp2.html", app1.View); + } } } diff --git a/src/Umbraco.Tests/Models/ContentExtensionsTests.cs b/src/Umbraco.Tests/Models/ContentExtensionsTests.cs index a19d28c295..38a93c0c35 100644 --- a/src/Umbraco.Tests/Models/ContentExtensionsTests.cs +++ b/src/Umbraco.Tests/Models/ContentExtensionsTests.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 1eccb5f2aa..76aced7081 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -2,12 +2,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.IO; using System.Linq; -using System.Web; using Moq; using Umbraco.Core; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; @@ -128,30 +127,6 @@ namespace Umbraco.Tests.Models Assert.That(content.Properties["title"].GetValue(), Is.EqualTo("This is the new title")); } - [Test] - public void Can_Set_Property_Value_As_HttpPostedFileBase() - { - // Arrange - var contentType = MockedContentTypes.CreateTextpageContentType(); - var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); - - var stream = new MemoryStream(System.Text.Encoding.Default.GetBytes("TestContent")); - var postedFileMock = new Mock(); - postedFileMock.Setup(x => x.ContentLength).Returns(Convert.ToInt32(stream.Length)); - postedFileMock.Setup(x => x.ContentType).Returns("text/plain"); - postedFileMock.Setup(x => x.FileName).Returns("sample.txt"); - postedFileMock.Setup(x => x.InputStream).Returns(stream); - - // Assert - content.SetValue("title", postedFileMock.Object); - - // Assert - Assert.That(content.Properties.Any(), Is.True); - Assert.That(content.Properties["title"], Is.Not.Null); - Assert.IsTrue(content.Properties["title"].GetValue().ToString().Contains("sample.txt")); - } - - [Test] public void Can_Clone_Content_With_Reset_Identity() { diff --git a/src/Umbraco.Tests/Models/MediaXmlTest.cs b/src/Umbraco.Tests/Models/MediaXmlTest.cs index 29c936adb9..492803ce17 100644 --- a/src/Umbraco.Tests/Models/MediaXmlTest.cs +++ b/src/Umbraco.Tests/Models/MediaXmlTest.cs @@ -29,7 +29,7 @@ namespace Umbraco.Tests.Models // reference, so static ctor runs, so event handlers register // and then, this will reset the width, height... because the file does not exist, of course ;-( - var ignored = new FileUploadPropertyEditor(Mock.Of(), new MediaFileSystem(Mock.Of())); + var ignored = new FileUploadPropertyEditor(Mock.Of(), new MediaFileSystem(Mock.Of()), Mock.Of()); var media = MockedMedia.CreateMediaImage(mediaType, -1); media.WriterId = -1; // else it's zero and that's not a user and it breaks the tests diff --git a/src/Umbraco.Tests/Models/TaskTests.cs b/src/Umbraco.Tests/Models/TaskTests.cs deleted file mode 100644 index 808c2884b7..0000000000 --- a/src/Umbraco.Tests/Models/TaskTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Diagnostics; -using NUnit.Framework; -using Umbraco.Core.Models; -using Umbraco.Core.Serialization; - -namespace Umbraco.Tests.Models -{ - [TestFixture] - public class TaskTests - { - [Test] - public void Can_Deep_Clone() - { - var item = new Task(new TaskType("test") {Id = 3}) - { - AssigneeUserId = 4, - Closed = true, - Comment = "blah", - CreateDate = DateTime.Now, - EntityId = 99, - Id = 2, - Key = Guid.NewGuid(), - OwnerUserId = 89, - TaskType = new TaskType("asdf") {Id = 99}, - UpdateDate = DateTime.Now - }; - - var clone = (Task) item.DeepClone(); - - Assert.AreNotSame(clone, item); - Assert.AreEqual(clone, item); - Assert.AreEqual(clone.CreateDate, item.CreateDate); - Assert.AreEqual(clone.UpdateDate, item.UpdateDate); - Assert.AreEqual(clone.AssigneeUserId, item.AssigneeUserId); - Assert.AreEqual(clone.Closed, item.Closed); - Assert.AreEqual(clone.Comment, item.Comment); - Assert.AreEqual(clone.EntityId, item.EntityId); - Assert.AreEqual(clone.Id, item.Id); - Assert.AreEqual(clone.Key, item.Key); - Assert.AreEqual(clone.OwnerUserId, item.OwnerUserId); - Assert.AreNotSame(clone.TaskType, item.TaskType); - Assert.AreEqual(clone.TaskType, item.TaskType); - - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) - { - Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); - } - } - - [Test] - public void Can_Serialize_Without_Error() - { - var ss = new SerializationService(new JsonNetSerializer()); - - var item = new Task(new TaskType("test") { Id = 3 }) - { - AssigneeUserId = 4, - Closed = true, - Comment = "blah", - CreateDate = DateTime.Now, - EntityId = 99, - Id = 2, - Key = Guid.NewGuid(), - OwnerUserId = 89, - TaskType = new TaskType("asdf") { Id = 99 }, - UpdateDate = DateTime.Now - }; - - var result = ss.ToStream(item); - var json = result.ResultStream.ToJsonString(); - Debug.Print(json); - } - - } -} diff --git a/src/Umbraco.Tests/Models/TaskTypeTests.cs b/src/Umbraco.Tests/Models/TaskTypeTests.cs deleted file mode 100644 index 5e31e8a1d8..0000000000 --- a/src/Umbraco.Tests/Models/TaskTypeTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Diagnostics; -using NUnit.Framework; -using Umbraco.Core.Models; -using Umbraco.Core.Serialization; - -namespace Umbraco.Tests.Models -{ - [TestFixture] - public class TaskTypeTests - { - [Test] - public void Can_Deep_Clone() - { - var item = new TaskType("test") - { - Id = 3, - Alias = "test", - CreateDate = DateTime.Now, - Key = Guid.NewGuid(), - UpdateDate = DateTime.Now - }; - - var clone = (TaskType)item.DeepClone(); - - Assert.AreNotSame(clone, item); - Assert.AreEqual(clone, item); - Assert.AreEqual(clone.CreateDate, item.CreateDate); - Assert.AreEqual(clone.Id, item.Id); - Assert.AreEqual(clone.Key, item.Key); - Assert.AreEqual(clone.Alias, item.Alias); - Assert.AreEqual(clone.UpdateDate, item.UpdateDate); - - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) - { - Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); - } - - } - - [Test] - public void Can_Serialize_Without_Error() - { - var ss = new SerializationService(new JsonNetSerializer()); - - var item = new TaskType("test") - { - Id = 3, - Alias = "test", - CreateDate = DateTime.Now, - Key = Guid.NewGuid(), - UpdateDate = DateTime.Now - }; - - var result = ss.ToStream(item); - var json = result.ResultStream.ToJsonString(); - Debug.Print(json); - } - } -} diff --git a/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs index a8cdbfcdac..62407d1ec5 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs @@ -1,22 +1,24 @@ using System.Linq; using NUnit.Framework; -using Umbraco.Core.Cache; using Umbraco.Core.Models; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; +using Umbraco.Core.Logging; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; namespace Umbraco.Tests.Persistence.Repositories { [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)] public class AuditRepositoryTest : TestWithDatabaseBase { + [Test] public void Can_Add_Audit_Entry() { @@ -24,7 +26,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = sp.CreateScope()) { var repo = new AuditRepository((IScopeAccessor) sp, Logger); - repo.Save(new AuditItem(-1, "This is a System audit trail", AuditType.System, 0)); + repo.Save(new AuditItem(-1, "This is a System audit trail", AuditType.System, -1)); var dtos = scope.Database.Fetch("WHERE id > -1"); @@ -43,8 +45,8 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 100; i++) { - repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, 0)); - repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, 0)); + repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, -1)); + repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, -1)); } scope.Complete(); @@ -61,6 +63,49 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Get_Paged_Items_By_User_Id_With_Query_And_Filter() + { + var sp = TestObjects.GetScopeProvider(Logger); + using (var scope = sp.CreateScope()) + { + var repo = new AuditRepository((IScopeAccessor)sp, Logger); + + for (var i = 0; i < 100; i++) + { + repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, -1)); + repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, -1)); + } + + scope.Complete(); + } + + using (var scope = sp.CreateScope()) + { + var repo = new AuditRepository((IScopeAccessor)sp, Logger); + + var query = sp.SqlContext.Query().Where(x => x.UserId == -1); + + try + { + scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + scope.Database.AsUmbracoDatabase().EnableSqlCount = true; + + var page = repo.GetPagedResultsByQuery(query, 0, 10, out var total, Direction.Descending, + new[] { AuditType.Publish }, + sp.SqlContext.Query().Where(x => x.UserId > -2)); + + Assert.AreEqual(10, page.Count()); + Assert.AreEqual(100, total); + } + finally + { + scope.Database.AsUmbracoDatabase().EnableSqlTrace = false; + scope.Database.AsUmbracoDatabase().EnableSqlCount = false; + } + } + } + [Test] public void Get_Paged_Items_With_AuditType_Filter() { @@ -71,8 +116,8 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 100; i++) { - repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, 0)); - repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, 0)); + repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, -1)); + repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, -1)); } scope.Complete(); @@ -102,8 +147,8 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 100; i++) { - repo.Save(new AuditItem(i, "Content created", AuditType.New, 0)); - repo.Save(new AuditItem(i, "Content published", AuditType.Publish, 0)); + repo.Save(new AuditItem(i, "Content created", AuditType.New, -1)); + repo.Save(new AuditItem(i, "Content published", AuditType.Publish, -1)); } scope.Complete(); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index 2a3b197218..c4105ba97e 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; +using Umbraco.Core.Services; using Umbraco.Tests.Testing; using Umbraco.Web.PropertyEditors; @@ -760,7 +761,7 @@ namespace Umbraco.Tests.Persistence.Repositories scope.Database.AsUmbracoDatabase().EnableSqlCount = true; var query = scope.SqlContext.Query().Where(x => x.ParentId == root.Id); - var result = repository.GetPage(query, 0, 20, out var totalRecords, "UpdateDate", Direction.Ascending, true); + var result = repository.GetPage(query, 0, 20, out var totalRecords, null, Ordering.By("UpdateDate")); Assert.AreEqual(25, totalRecords); foreach (var r in result) @@ -803,12 +804,12 @@ namespace Umbraco.Tests.Persistence.Repositories scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; scope.Database.AsUmbracoDatabase().EnableSqlCount = true; - var result = repository.GetPage(query, 0, 2, out var totalRecords, "title", Direction.Ascending, false); + var result = repository.GetPage(query, 0, 2, out var totalRecords, null, Ordering.By("title", isCustomField: true)); Assert.AreEqual(3, totalRecords); Assert.AreEqual(2, result.Count()); - result = repository.GetPage(query, 1, 2, out totalRecords, "title", Direction.Ascending, false); + result = repository.GetPage(query, 1, 2, out totalRecords, null, Ordering.By("title", isCustomField: true)); Assert.AreEqual(1, result.Count()); } @@ -835,7 +836,7 @@ namespace Umbraco.Tests.Persistence.Repositories scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; scope.Database.AsUmbracoDatabase().EnableSqlCount = true; - var result = repository.GetPage(query, 0, 1, out var totalRecords, "Name", Direction.Ascending, true); + var result = repository.GetPage(query, 0, 1, out var totalRecords, null, Ordering.By("Name")); Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); Assert.That(result.Count(), Is.EqualTo(1)); @@ -858,7 +859,7 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository((IScopeAccessor)provider, out _); var query = scope.SqlContext.Query().Where(x => x.Level == 2); - var result = repository.GetPage(query, 1, 1, out var totalRecords, "Name", Direction.Ascending, true); + var result = repository.GetPage(query, 1, 1, out var totalRecords, null, Ordering.By("Name")); Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); Assert.That(result.Count(), Is.EqualTo(1)); @@ -875,7 +876,7 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository((IScopeAccessor)provider, out _); var query = scope.SqlContext.Query().Where(x => x.Level == 2); - var result = repository.GetPage(query, 0, 2, out var totalRecords, "Name", Direction.Ascending, true); + var result = repository.GetPage(query, 0, 2, out var totalRecords, null, Ordering.By("Name")); Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); Assert.That(result.Count(), Is.EqualTo(2)); @@ -892,7 +893,7 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository((IScopeAccessor)provider, out _); var query = scope.SqlContext.Query().Where(x => x.Level == 2); - var result = repository.GetPage(query, 0, 1, out var totalRecords, "Name", Direction.Descending, true); + var result = repository.GetPage(query, 0, 1, out var totalRecords, null, Ordering.By("Name", Direction.Descending)); Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); Assert.That(result.Count(), Is.EqualTo(1)); @@ -911,7 +912,7 @@ namespace Umbraco.Tests.Persistence.Repositories var query = scope.SqlContext.Query().Where(x => x.Level == 2); var filterQuery = scope.SqlContext.Query().Where(x => x.Name.Contains("Page 2")); - var result = repository.GetPage(query, 0, 1, out var totalRecords, "Name", Direction.Ascending, true, filterQuery); + var result = repository.GetPage(query, 0, 1, out var totalRecords, filterQuery, Ordering.By("Name")); Assert.That(totalRecords, Is.EqualTo(1)); Assert.That(result.Count(), Is.EqualTo(1)); @@ -930,7 +931,7 @@ namespace Umbraco.Tests.Persistence.Repositories var query = scope.SqlContext.Query().Where(x => x.Level == 2); var filterQuery = scope.SqlContext.Query().Where(x => x.Name.Contains("text")); - var result = repository.GetPage(query, 0, 1, out var totalRecords, "Name", Direction.Ascending, true, filterQuery); + var result = repository.GetPage(query, 0, 1, out var totalRecords, filterQuery, Ordering.By("Name")); Assert.That(totalRecords, Is.EqualTo(2)); Assert.That(result.Count(), Is.EqualTo(1)); diff --git a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs index 7be52c7212..2f91c602af 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs @@ -47,6 +47,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(language.HasIdentity, Is.True); Assert.That(language.CultureName, Is.EqualTo("English (United States)")); Assert.That(language.IsoCode, Is.EqualTo("en-US")); + Assert.That(language.FallbackLanguageId, Is.Null); } } @@ -61,7 +62,8 @@ namespace Umbraco.Tests.Persistence.Repositories var au = CultureInfo.GetCultureInfo("en-AU"); var language = (ILanguage)new Language(au.Name) { - CultureName = au.DisplayName + CultureName = au.DisplayName, + FallbackLanguageId = 1 }; repository.Save(language); @@ -73,6 +75,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(language.HasIdentity, Is.True); Assert.That(language.CultureName, Is.EqualTo(au.DisplayName)); Assert.That(language.IsoCode, Is.EqualTo(au.Name)); + Assert.That(language.FallbackLanguageId, Is.EqualTo(1)); } } @@ -182,14 +185,15 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository(provider); // Act - var languageBR = new Language("pt-BR") {CultureName = "pt-BR"}; + var languageBR = new Language("pt-BR") { CultureName = "pt-BR" }; repository.Save(languageBR); // Assert Assert.That(languageBR.HasIdentity, Is.True); Assert.That(languageBR.Id, Is.EqualTo(6)); //With 5 existing entries the Id should be 6 - Assert.IsFalse(languageBR.IsDefaultVariantLanguage); - Assert.IsFalse(languageBR.Mandatory); + Assert.IsFalse(languageBR.IsDefault); + Assert.IsFalse(languageBR.IsMandatory); + Assert.IsNull(languageBR.FallbackLanguageId); } } @@ -203,14 +207,39 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository(provider); // Act - var languageBR = new Language("pt-BR") { CultureName = "pt-BR", IsDefaultVariantLanguage = true, Mandatory = true }; + var languageBR = new Language("pt-BR") { CultureName = "pt-BR", IsDefault = true, IsMandatory = true }; repository.Save(languageBR); // Assert Assert.That(languageBR.HasIdentity, Is.True); Assert.That(languageBR.Id, Is.EqualTo(6)); //With 5 existing entries the Id should be 6 - Assert.IsTrue(languageBR.IsDefaultVariantLanguage); - Assert.IsTrue(languageBR.Mandatory); + Assert.IsTrue(languageBR.IsDefault); + Assert.IsTrue(languageBR.IsMandatory); + Assert.IsNull(languageBR.FallbackLanguageId); + } + } + + [Test] + public void Can_Perform_Add_On_LanguageRepository_With_Fallback_Language() + { + // Arrange + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repository = CreateRepository(provider); + + // Act + var languageBR = new Language("pt-BR") + { + CultureName = "pt-BR", + FallbackLanguageId = 1 + }; + repository.Save(languageBR); + + // Assert + Assert.That(languageBR.HasIdentity, Is.True); + Assert.That(languageBR.Id, Is.EqualTo(6)); //With 5 existing entries the Id should be 6 + Assert.That(languageBR.FallbackLanguageId, Is.EqualTo(1)); } } @@ -223,24 +252,22 @@ namespace Umbraco.Tests.Persistence.Repositories { var repository = CreateRepository(provider); - var languageBR = (ILanguage)new Language("pt-BR") { CultureName = "pt-BR", IsDefaultVariantLanguage = true, Mandatory = true }; + var languageBR = (ILanguage)new Language("pt-BR") { CultureName = "pt-BR", IsDefault = true, IsMandatory = true }; repository.Save(languageBR); var languageEN = new Language("en-AU") { CultureName = "en-AU" }; repository.Save(languageEN); - Assert.IsTrue(languageBR.IsDefaultVariantLanguage); - Assert.IsTrue(languageBR.Mandatory); + Assert.IsTrue(languageBR.IsDefault); + Assert.IsTrue(languageBR.IsMandatory); // Act - - var languageNZ = new Language("en-NZ") { CultureName = "en-NZ", IsDefaultVariantLanguage = true, Mandatory = true }; + var languageNZ = new Language("en-NZ") { CultureName = "en-NZ", IsDefault = true, IsMandatory = true }; repository.Save(languageNZ); languageBR = repository.Get(languageBR.Id); // Assert - - Assert.IsFalse(languageBR.IsDefaultVariantLanguage); - Assert.IsTrue(languageNZ.IsDefaultVariantLanguage); + Assert.IsFalse(languageBR.IsDefault); + Assert.IsTrue(languageNZ.IsDefault); } } @@ -257,6 +284,7 @@ namespace Umbraco.Tests.Persistence.Repositories var language = repository.Get(5); language.IsoCode = "pt-BR"; language.CultureName = "pt-BR"; + language.FallbackLanguageId = 1; repository.Save(language); @@ -266,6 +294,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(languageUpdated, Is.Not.Null); Assert.That(languageUpdated.IsoCode, Is.EqualTo("pt-BR")); Assert.That(languageUpdated.CultureName, Is.EqualTo("pt-BR")); + Assert.That(languageUpdated.FallbackLanguageId, Is.EqualTo(1)); } } @@ -289,6 +318,30 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Perform_Delete_On_LanguageRepository_With_Language_Used_As_Fallback() + { + // Arrange + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + // Add language to delete as a fall-back language to another one + var repository = CreateRepository(provider); + var languageToFallbackFrom = repository.Get(5); + languageToFallbackFrom.FallbackLanguageId = 2; // fall back to #2 (something we can delete) + repository.Save(languageToFallbackFrom); + + // delete #2 + var languageToDelete = repository.Get(2); + repository.Delete(languageToDelete); + + var exists = repository.Exists(2); + + // has been deleted + Assert.That(exists, Is.False); + } + } + [Test] public void Can_Perform_Exists_On_LanguageRepository() { @@ -314,8 +367,10 @@ namespace Umbraco.Tests.Persistence.Repositories base.TearDown(); } - public void CreateTestData() + private void CreateTestData() { + //Id 1 is en-US - when Umbraco is installed + var languageDK = new Language("da-DK") { CultureName = "da-DK" }; ServiceContext.LocalizationService.Save(languageDK);//Id 2 diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 87759db3df..d1e7f96ff3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -326,7 +326,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = scope.SqlContext.Query().Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPage(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true); + var result = repository.GetPage(query, 0, 1, out totalRecords, null, Ordering.By("SortOrder")); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -348,7 +348,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = scope.SqlContext.Query().Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPage(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending, true); + var result = repository.GetPage(query, 1, 1, out totalRecords, null, Ordering.By("SortOrder")); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -370,7 +370,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = scope.SqlContext.Query().Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPage(query, 0, 2, out totalRecords, "SortOrder", Direction.Ascending, true); + var result = repository.GetPage(query, 0, 2, out totalRecords, null, Ordering.By("SortOrder")); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -392,7 +392,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = scope.SqlContext.Query().Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPage(query, 0, 1, out totalRecords, "SortOrder", Direction.Descending, true); + var result = repository.GetPage(query, 0, 1, out totalRecords, null, Ordering.By("SortOrder", Direction.Descending)); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -413,8 +413,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = scope.SqlContext.Query().Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPage(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true); + var result = repository.GetPage(query, 0, 1, out var totalRecords, null, Ordering.By("Name")); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -435,10 +434,9 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = scope.SqlContext.Query().Where(x => x.Level == 2); - long totalRecords; var filter = scope.SqlContext.Query().Where(x => x.Name.Contains("File")); - var result = repository.GetPage(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, filter); + var result = repository.GetPage(query, 0, 1, out var totalRecords, filter, Ordering.By("SortOrder")); // Assert Assert.That(totalRecords, Is.EqualTo(1)); @@ -454,15 +452,13 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - MediaTypeRepository mediaTypeRepository; - var repository = CreateRepository(provider, out mediaTypeRepository); + var repository = CreateRepository(provider, out _); // Act var query = scope.SqlContext.Query().Where(x => x.Level == 2); - long totalRecords; var filter = scope.SqlContext.Query().Where(x => x.Name.Contains("Test")); - var result = repository.GetPage(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, filter); + var result = repository.GetPage(query, 0, 1, out var totalRecords, filter, Ordering.By("SortOrder")); // Assert Assert.That(totalRecords, Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs deleted file mode 100644 index a4b01aef9e..0000000000 --- a/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs +++ /dev/null @@ -1,230 +0,0 @@ -using System; -using System.Linq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.Repositories.Implement; -using Umbraco.Core.Scoping; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Persistence.Repositories -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class TaskRepositoryTest : TestWithDatabaseBase - { - [Test] - public void Can_Delete() - { - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var repo = new TaskRepository((IScopeAccessor) provider, Logger); - - var created = DateTime.Now; - var task = new Task(new TaskType("asdfasdf")) - { - AssigneeUserId = Constants.Security.SuperUserId, - Closed = false, - Comment = "hello world", - EntityId = -1, - OwnerUserId = Constants.Security.SuperUserId - }; - repo.Save(task); - - - repo.Delete(task); - - - task = repo.Get(task.Id); - Assert.IsNull(task); - } - } - - [Test] - public void Can_Add() - { - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var repo = new TaskRepository((IScopeAccessor) provider, Logger); - - var created = DateTime.Now; - repo.Save(new Task(new TaskType("asdfasdf")) - { - AssigneeUserId = Constants.Security.SuperUserId, - Closed = false, - Comment = "hello world", - EntityId = -1, - OwnerUserId = Constants.Security.SuperUserId - }); - - - var found = repo.GetMany().ToArray(); - - Assert.AreEqual(1, found.Length); - Assert.AreEqual(Constants.Security.SuperUserId, found.First().AssigneeUserId); - Assert.AreEqual(false, found.First().Closed); - Assert.AreEqual("hello world", found.First().Comment); - Assert.GreaterOrEqual(found.First().CreateDate.TruncateTo(DateTimeExtensions.DateTruncate.Second), created.TruncateTo(DateTimeExtensions.DateTruncate.Second)); - Assert.AreEqual(-1, found.First().EntityId); - Assert.AreEqual(Constants.Security.SuperUserId, found.First().OwnerUserId); - Assert.AreEqual(true, found.First().HasIdentity); - Assert.AreEqual(true, found.First().TaskType.HasIdentity); - } - } - - [Test] - public void Can_Update() - { - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var repo = new TaskRepository((IScopeAccessor) provider, Logger); - - var task = new Task(new TaskType("asdfasdf")) - { - AssigneeUserId = Constants.Security.SuperUserId, - Closed = false, - Comment = "hello world", - EntityId = -1, - OwnerUserId = Constants.Security.SuperUserId - }; - - repo.Save(task); - - - //re-get - task = repo.Get(task.Id); - - task.Comment = "blah"; - task.Closed = true; - - repo.Save(task); - - - //re-get - task = repo.Get(task.Id); - - Assert.AreEqual(true, task.Closed); - Assert.AreEqual("blah", task.Comment); - } - } - - [Test] - public void Get_By_Id() - { - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var repo = new TaskRepository((IScopeAccessor) provider, Logger); - - var task = new Task(new TaskType("asdfasdf")) - { - AssigneeUserId = Constants.Security.SuperUserId, - Closed = false, - Comment = "hello world", - EntityId = -1, - OwnerUserId = Constants.Security.SuperUserId - }; - - repo.Save(task); - - - //re-get - task = repo.Get(task.Id); - - Assert.IsNotNull(task); - } - } - - [Test] - public void Get_All() - { - CreateTestData(false, 20); - - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var repo = new TaskRepository((IScopeAccessor) provider, Logger); - - var found = repo.GetMany().ToArray(); - Assert.AreEqual(20, found.Count()); - } - } - - [Test] - public void Get_All_With_Closed() - { - CreateTestData(false, 10); - CreateTestData(true, 5); - - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var repo = new TaskRepository((IScopeAccessor) provider, Logger); - - var found = repo.GetTasks(includeClosed: true).ToArray(); - Assert.AreEqual(15, found.Count()); - } - } - - [Test] - public void Get_All_With_Node_Id() - { - CreateTestData(false, 10, -20); - CreateTestData(false, 5, -21); - - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var repo = new TaskRepository((IScopeAccessor) provider, Logger); - - var found = repo.GetTasks(itemId:-20).ToArray(); - Assert.AreEqual(10, found.Count()); - } - } - - [Test] - public void Get_All_Without_Closed() - { - CreateTestData(false, 10); - CreateTestData(true, 5); - - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var repo = new TaskRepository((IScopeAccessor) provider, Logger); - - var found = repo.GetTasks(includeClosed: false); - Assert.AreEqual(10, found.Count()); - } - } - - private void CreateTestData(bool closed, int count, int entityId = -1) - { - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var repo = new TaskRepository((IScopeAccessor) provider, Logger); - - for (int i = 0; i < count; i++) - { - repo.Save(new Task(new TaskType("asdfasdf")) - { - AssigneeUserId = Constants.Security.SuperUserId, - Closed = closed, - Comment = "hello world " + i, - EntityId = entityId, - OwnerUserId = Constants.Security.SuperUserId - }); - } - - scope.Complete(); - } - } - } -} diff --git a/src/Umbraco.Tests/Persistence/Repositories/TaskTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TaskTypeRepositoryTest.cs deleted file mode 100644 index 7f159215b7..0000000000 --- a/src/Umbraco.Tests/Persistence/Repositories/TaskTypeRepositoryTest.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Linq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.Repositories.Implement; -using Umbraco.Core.Scoping; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Persistence.Repositories -{ - [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class TaskTypeRepositoryTest : TestWithDatabaseBase - { - [Test] - public void Can_Delete() - { - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) - { - var taskType = new TaskType("asdfasdf"); - var repo = new TaskRepository((IScopeAccessor) provider, Logger); - var taskTypeRepo = new TaskTypeRepository((IScopeAccessor) provider, Logger); - - var created = DateTime.Now; - var task = new Task(taskType) - { - AssigneeUserId = Constants.Security.SuperUserId, - Closed = false, - Comment = "hello world", - EntityId = -1, - OwnerUserId = Constants.Security.SuperUserId - }; - repo.Save(task); - - var alltasktypes = taskTypeRepo.GetMany(); - - taskTypeRepo.Delete(taskType); - - Assert.AreEqual(alltasktypes.Count() - 1, taskTypeRepo.GetMany().Count()); - Assert.AreEqual(0, repo.GetMany().Count()); - } - - - } - } -} diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index b9bbfbb363..3ec9d572bf 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -429,32 +429,6 @@ namespace Umbraco.Tests.Persistence.Repositories } } - [Test] - public void Can_Get_Template_Tree() - { - // Arrange - using (ScopeProvider.CreateScope()) - { - var repository = CreateRepository(ScopeProvider); - - CreateHierarchy(repository); - - // Act - var rootNode = repository.GetTemplateNode("parent"); - - // Assert - Assert.IsNotNull(repository.FindTemplateInTree(rootNode, "parent")); - Assert.IsNotNull(repository.FindTemplateInTree(rootNode, "child1")); - Assert.IsNotNull(repository.FindTemplateInTree(rootNode, "child2")); - Assert.IsNotNull(repository.FindTemplateInTree(rootNode, "toddler1")); - Assert.IsNotNull(repository.FindTemplateInTree(rootNode, "toddler2")); - Assert.IsNotNull(repository.FindTemplateInTree(rootNode, "toddler3")); - Assert.IsNotNull(repository.FindTemplateInTree(rootNode, "toddler4")); - Assert.IsNotNull(repository.FindTemplateInTree(rootNode, "baby1")); - Assert.IsNotNull(repository.FindTemplateInTree(rootNode, "baby2")); - } - } - [Test] public void Can_Get_All() { diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 64615c1950..847972cc50 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -8,17 +8,19 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; +using Umbraco.Core.Persistence; namespace Umbraco.Tests.Persistence.Repositories { [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true, Logger = UmbracoTestOptions.Logger.Console)] public class UserRepositoryTest : TestWithDatabaseBase { private MediaRepository CreateMediaRepository(IScopeProvider provider, out IMediaTypeRepository mediaTypeRepository) @@ -50,14 +52,14 @@ namespace Umbraco.Tests.Persistence.Repositories private UserRepository CreateRepository(IScopeProvider provider) { var accessor = (IScopeAccessor) provider; - var repository = new UserRepository(accessor, CacheHelper.Disabled, Mock.Of(), Mock.Of(), TestObjects.GetGlobalSettings()); + var repository = new UserRepository(accessor, CacheHelper.Disabled, Logger, Mappers, TestObjects.GetGlobalSettings()); return repository; } private UserGroupRepository CreateUserGroupRepository(IScopeProvider provider) { var accessor = (IScopeAccessor) provider; - return new UserGroupRepository(accessor, CacheHelper.Disabled, Mock.Of()); + return new UserGroupRepository(accessor, CacheHelper.Disabled, Logger); } [Test] @@ -338,7 +340,72 @@ namespace Umbraco.Tests.Persistence.Repositories var result = repository.Count(query); // Assert - Assert.That(result, Is.GreaterThanOrEqualTo(2)); + Assert.AreEqual(2, result); + } + } + + [Test] + public void Can_Get_Paged_Results_By_Query_And_Filter_And_Groups() + { + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repository = CreateRepository(provider); + + var users = CreateAndCommitMultipleUsers(repository); + var query = provider.SqlContext.Query().Where(x => x.Username == "TestUser1" || x.Username == "TestUser2"); + + try + { + scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + scope.Database.AsUmbracoDatabase().EnableSqlCount = true; + + // Act + var result = repository.GetPagedResultsByQuery(query, 0, 10, out var totalRecs, user => user.Id, Direction.Ascending, + excludeUserGroups: new[] { Constants.Security.TranslatorGroupAlias }, + filter: provider.SqlContext.Query().Where(x => x.Id > -1)); + + // Assert + Assert.AreEqual(2, totalRecs); + } + finally + { + scope.Database.AsUmbracoDatabase().EnableSqlTrace = false; + scope.Database.AsUmbracoDatabase().EnableSqlCount = false; + } + } + + } + + [Test] + public void Can_Get_Paged_Results_With_Filter_And_Groups() + { + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repository = CreateRepository(provider); + + var users = CreateAndCommitMultipleUsers(repository); + + try + { + scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + scope.Database.AsUmbracoDatabase().EnableSqlCount = true; + + // Act + var result = repository.GetPagedResultsByQuery(null, 0, 10, out var totalRecs, user => user.Id, Direction.Ascending, + includeUserGroups: new[] { Constants.Security.AdminGroupAlias, Constants.Security.SensitiveDataGroupAlias }, + excludeUserGroups: new[] { Constants.Security.TranslatorGroupAlias }, + filter: provider.SqlContext.Query().Where(x => x.Id == -1)); + + // Assert + Assert.AreEqual(1, totalRecs); + } + finally + { + scope.Database.AsUmbracoDatabase().EnableSqlTrace = false; + scope.Database.AsUmbracoDatabase().EnableSqlCount = false; + } } } diff --git a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs index ba9a6b219c..38daa2c1a7 100644 --- a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs @@ -450,35 +450,6 @@ namespace Umbraco.Tests.Persistence } } - [Test] - public void Can_Create_cmsTask_Table() - { - using (var scope = ScopeProvider.CreateScope()) - { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of()); - - helper.CreateTable(); - helper.CreateTable(); - helper.CreateTable(); - helper.CreateTable(); - - scope.Complete(); - } - } - - [Test] - public void Can_Create_cmsTaskType_Table() - { - using (var scope = ScopeProvider.CreateScope()) - { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of()); - - helper.CreateTable(); - - scope.Complete(); - } - } - [Test] public void Can_Create_umbracoUser_Table() { diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index f9093ad6fe..cdc3918fc3 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Models; using Umbraco.Web; @@ -71,10 +72,11 @@ namespace Umbraco.Tests.PropertyEditors Current.Container.RegisterSingleton(f => Mock.Of()); Current.Container.RegisterSingleton(f => Mock.Of()); + Current.Container.RegisterSingleton(f => Mock.Of()); var mediaFileSystem = new MediaFileSystem(Mock.Of()); var dataTypeService = new TestObjects.TestDataTypeService( - new DataType(new ImageCropperPropertyEditor(Mock.Of(), mediaFileSystem, Mock.Of())) { Id = 1 }); + new DataType(new ImageCropperPropertyEditor(Mock.Of(), mediaFileSystem, Mock.Of(), Mock.Of())) { Id = 1 }); var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs new file mode 100644 index 0000000000..2288ff80c2 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Composing; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Tests.Testing; +using Umbraco.Web; + +namespace Umbraco.Tests.PublishedContent +{ + [TestFixture] + [UmbracoTest(PluginManager = UmbracoTestOptions.PluginManager.PerFixture)] + public class PublishedContentLanguageVariantTests : PublishedContentSnapshotTestBase + { + protected override void Compose() + { + base.Compose(); + + Container.RegisterSingleton(_ => GetServiceContext()); + } + + protected ServiceContext GetServiceContext() + { + var serviceContext = TestObjects.GetServiceContextMock(Container); + MockLocalizationService(serviceContext); + return serviceContext; + } + + private static void MockLocalizationService(ServiceContext serviceContext) + { + // Set up languages. + // Spanish falls back to English and Italian to Spanish (and then to English). + // French has no fall back. + // Danish, Swedish and Norweigan create an invalid loop. + var languages = new List + { + new Language("en-US") { Id = 1, CultureName = "English", IsDefault = true }, + new Language("fr") { Id = 2, CultureName = "French" }, + new Language("es") { Id = 3, CultureName = "Spanish", FallbackLanguageId = 1 }, + new Language("it") { Id = 4, CultureName = "Italian", FallbackLanguageId = 3 }, + new Language("de") { Id = 5, CultureName = "German" }, + new Language("da") { Id = 6, CultureName = "Danish", FallbackLanguageId = 8 }, + new Language("sv") { Id = 7, CultureName = "Swedish", FallbackLanguageId = 6 }, + new Language("no") { Id = 8, CultureName = "Norweigan", FallbackLanguageId = 7 }, + new Language("nl") { Id = 9, CultureName = "Dutch", FallbackLanguageId = 1 } + }; + + var localizationService = Mock.Get(serviceContext.LocalizationService); + localizationService.Setup(x => x.GetAllLanguages()).Returns(languages); + localizationService.Setup(x => x.GetLanguageById(It.IsAny())) + .Returns((int id) => languages.SingleOrDefault(y => y.Id == id)); + localizationService.Setup(x => x.GetLanguageByIsoCode(It.IsAny())) + .Returns((string c) => languages.SingleOrDefault(y => y.IsoCode == c)); + } + + internal override void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache) + { + var prop1Type = factory.CreatePropertyType("prop1", 1); + var welcomeType = factory.CreatePropertyType("welcomeText", 1); + var welcome2Type = factory.CreatePropertyType("welcomeText2", 1); + var props = new[] + { + prop1Type, + welcomeType, + welcome2Type, + }; + var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), props); + + var prop1 = new SolidPublishedPropertyWithLanguageVariants + { + Alias = "welcomeText", + PropertyType = welcomeType + }; + prop1.SetSourceValue("en-US", "Welcome", true); + prop1.SetValue("en-US", "Welcome", true); + prop1.SetSourceValue("de", "Willkommen"); + prop1.SetValue("de", "Willkommen"); + prop1.SetSourceValue("nl", "Welkom"); + prop1.SetValue("nl", "Welkom"); + + var prop2 = new SolidPublishedPropertyWithLanguageVariants + { + Alias = "welcomeText2", + PropertyType = welcome2Type + }; + prop2.SetSourceValue("en-US", "Welcome", true); + prop2.SetValue("en-US", "Welcome", true); + + var prop3 = new SolidPublishedPropertyWithLanguageVariants + { + Alias = "welcomeText", + PropertyType = welcomeType + }; + prop3.SetSourceValue("en-US", "Welcome", true); + prop3.SetValue("en-US", "Welcome", true); + + var item1 = new SolidPublishedContent(contentType1) + { + Id = 1, + SortOrder = 0, + Name = "Content 1", + UrlSegment = "content-1", + Path = "/1", + Level = 1, + Url = "/content-1", + ParentId = -1, + ChildIds = new[] { 2 }, + Properties = new Collection + { + prop1, prop2 + } + }; + + var item2 = new SolidPublishedContent(contentType1) + { + Id = 2, + SortOrder = 0, + Name = "Content 2", + UrlSegment = "content-2", + Path = "/1/2", + Level = 2, + Url = "/content-1/content-2", + ParentId = 1, + ChildIds = new int[] { }, + Properties = new Collection + { + prop3 + } + }; + + item1.Children = new List { item2 }; + item2.Parent = item1; + + cache.Add(item1); + cache.Add(item2); + } + + [Test] + public void Can_Get_Content_For_Populated_Requested_Language() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var value = content.Value("welcomeText", "en-US"); + Assert.AreEqual("Welcome", value); + } + + [Test] + public void Can_Get_Content_For_Populated_Requested_Non_Default_Language() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var value = content.Value("welcomeText", "de"); + Assert.AreEqual("Willkommen", value); + } + + [Test] + public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_Without_Fallback() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var value = content.Value("welcomeText", "fr"); + Assert.IsNull(value); + } + + [Test] + public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Unless_Requested() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var value = content.Value("welcomeText", "es"); + Assert.IsNull(value); + } + + [Test] + public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var value = content.Value("welcomeText", "es", fallback: Fallback.ToLanguage); + Assert.AreEqual("Welcome", value); + } + + [Test] + public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Over_Two_Levels() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var value = content.Value("welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.Ancestors)); + Assert.AreEqual("Welcome", value); + } + + [Test] + public void Do_Not_GetContent_For_Unpopulated_Requested_Language_With_Fallback_Over_That_Loops() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var value = content.Value("welcomeText", "no", fallback: Fallback.ToLanguage); + Assert.IsNull(value); + } + + [Test] + public void Do_Not_Get_Content_Recursively_Unless_Requested() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First(); + var value = content.Value("welcomeText2"); + Assert.IsNull(value); + } + + [Test] + public void Can_Get_Content_Recursively() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First(); + var value = content.Value("welcomeText2", fallback: Fallback.ToAncestors); + Assert.AreEqual("Welcome", value); + } + + [Test] + public void Can_Get_Content_With_Recursive_Priority() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First(); + var value = content.Value("welcomeText", "nl", fallback: Fallback.To(Fallback.Ancestors, Fallback.Language)); + + // No Dutch value is directly assigned. Check has fallen back to Dutch value from parent. + Assert.AreEqual("Welkom", value); + } + + [Test] + public void Can_Get_Content_With_Fallback_Language_Priority() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First(); + var value = content.Value("welcomeText", "nl", fallback: Fallback.ToLanguage); + + // No Dutch value is directly assigned. Check has fallen back to English value from language variant. + Assert.AreEqual("Welcome", value); + } + + [Test] + public void Throws_For_Non_Supported_Fallback() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First(); + Assert.Throws(() => content.Value("welcomeText", "nl", fallback: Fallback.To(999))); + } + + [Test] + public void Can_Fallback_To_Default_Value() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First(); + + // no Dutch value is assigned, so getting null + var value = content.Value("welcomeText", "nl"); + Assert.IsNull(value); + + // even if we 'just' provide a default value + value = content.Value("welcomeText", "nl", defaultValue: "woop"); + Assert.IsNull(value); + + // but it works with proper fallback settings + value = content.Value("welcomeText", "nl", fallback: Fallback.ToDefaultValue, defaultValue: "woop"); + Assert.AreEqual("woop", value); + } + + [Test] + public void Can_Have_Custom_Default_Value() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First(); + + // hack the value, pretend the converter would return something + var prop = content.GetProperty("welcomeText") as SolidPublishedPropertyWithLanguageVariants; + Assert.IsNotNull(prop); + prop.SetValue("nl", "nope"); // HasValue false but getting value returns this + + // there is an EN value + var value = content.Value("welcomeText", "en-US"); + Assert.AreEqual("Welcome", value); + + // there is no NL value and we get the 'converted' value + value = content.Value("welcomeText", "nl"); + Assert.AreEqual("nope", value); + + // but it works with proper fallback settings + value = content.Value("welcomeText", "nl", fallback: Fallback.ToDefaultValue, defaultValue: "woop"); + Assert.AreEqual("woop", value); + } + } +} diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index 0f7212a0b4..9751a7ae6a 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -1,90 +1,95 @@ -using System; +using System.Collections.ObjectModel; using System.Linq; -using System.Collections.ObjectModel; -using System.Web.Routing; -using Moq; using NUnit.Framework; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.Routing; -using Umbraco.Web.Security; -using Umbraco.Core.Composing; -using Current = Umbraco.Core.Composing.Current; using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; -using Umbraco.Tests.Testing.Objects.Accessors; namespace Umbraco.Tests.PublishedContent { [TestFixture] [UmbracoTest(PluginManager = UmbracoTestOptions.PluginManager.PerFixture)] - public class PublishedContentMoreTests : PublishedContentTestBase + public class PublishedContentMoreTests : PublishedContentSnapshotTestBase { - // read http://stackoverflow.com/questions/7713326/extension-method-that-works-on-ienumerablet-and-iqueryablet - // and http://msmvps.com/blogs/jon_skeet/archive/2010/10/28/overloading-and-generic-constraints.aspx - // and http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx - - public override void SetUp() + internal override void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache) { - base.SetUp(); + var props = new[] + { + factory.CreatePropertyType("prop1", 1), + }; + var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), props); + var contentType2 = factory.CreateContentType(2, "ContentType2", Enumerable.Empty(), props); + var contentType2Sub = factory.CreateContentType(3, "ContentType2Sub", Enumerable.Empty(), props); - var umbracoContext = GetUmbracoContext(); - Umbraco.Web.Composing.Current.UmbracoContextAccessor.UmbracoContext = umbracoContext; - } + cache.Add(new SolidPublishedContent(contentType1) + { + Id = 1, + SortOrder = 0, + Name = "Content 1", + UrlSegment = "content-1", + Path = "/1", + Level = 1, + Url = "/content-1", + ParentId = -1, + ChildIds = new int[] { }, + Properties = new Collection + { + new SolidPublishedProperty + { + Alias = "prop1", + SolidHasValue = true, + SolidValue = 1234, + SolidSourceValue = "1234" + } + } + }); - protected override void Compose() - { - base.Compose(); + cache.Add(new SolidPublishedContent(contentType2) + { + Id = 2, + SortOrder = 1, + Name = "Content 2", + UrlSegment = "content-2", + Path = "/2", + Level = 1, + Url = "/content-2", + ParentId = -1, + ChildIds = new int[] { }, + Properties = new Collection + { + new SolidPublishedProperty + { + Alias = "prop1", + SolidHasValue = true, + SolidValue = 1234, + SolidSourceValue = "1234" + } + } + }); - Container.RegisterSingleton(f => new PublishedModelFactory(f.GetInstance().GetTypes())); - } - - protected override TypeLoader CreatePluginManager(IContainer f) - { - var pluginManager = base.CreatePluginManager(f); - - // this is so the model factory looks into the test assembly - pluginManager.AssembliesToScan = pluginManager.AssembliesToScan - .Union(new[] { typeof (PublishedContentMoreTests).Assembly }) - .ToList(); - - return pluginManager; - } - - private UmbracoContext GetUmbracoContext() - { - RouteData routeData = null; - - var publishedSnapshot = CreatePublishedSnapshot(); - - var publishedSnapshotService = new Mock(); - publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(publishedSnapshot); - - var globalSettings = TestObjects.GetGlobalSettings(); - - var httpContext = GetHttpContextFactory("http://umbraco.local/", routeData).HttpContext; - var umbracoContext = new UmbracoContext( - httpContext, - publishedSnapshotService.Object, - new WebSecurity(httpContext, Current.Services.UserService, globalSettings), - TestObjects.GetUmbracoSettings(), - Enumerable.Empty(), - globalSettings, - new TestVariationContextAccessor()); - - return umbracoContext; - } - - public override void TearDown() - { - base.TearDown(); - - Current.Reset(); + cache.Add(new SolidPublishedContent(contentType2Sub) + { + Id = 3, + SortOrder = 2, + Name = "Content 2Sub", + UrlSegment = "content-2sub", + Path = "/3", + Level = 1, + Url = "/content-2sub", + ParentId = -1, + ChildIds = new int[] { }, + Properties = new Collection + { + new SolidPublishedProperty + { + Alias = "prop1", + SolidHasValue = true, + SolidValue = 1234, + SolidSourceValue = "1234" + } + } + }); } [Test] @@ -196,95 +201,5 @@ namespace Umbraco.Tests.PublishedContent Assert.AreEqual(1, result[0].Id); Assert.AreEqual(2, result[1].Id); } - - private static SolidPublishedSnapshot CreatePublishedSnapshot() - { - var dataTypeService = new TestObjects.TestDataTypeService( - new DataType(new VoidEditor(Mock.Of())) { Id = 1 }); - - var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); - var caches = new SolidPublishedSnapshot(); - var cache = caches.InnerContentCache; - - var props = new[] - { - factory.CreatePropertyType("prop1", 1), - }; - - var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), props); - var contentType2 = factory.CreateContentType(2, "ContentType2", Enumerable.Empty(), props); - var contentType2Sub = factory.CreateContentType(3, "ContentType2Sub", Enumerable.Empty(), props); - - cache.Add(new SolidPublishedContent(contentType1) - { - Id = 1, - SortOrder = 0, - Name = "Content 1", - UrlSegment = "content-1", - Path = "/1", - Level = 1, - Url = "/content-1", - ParentId = -1, - ChildIds = new int[] {}, - Properties = new Collection - { - new SolidPublishedProperty - { - Alias = "prop1", - SolidHasValue = true, - SolidValue = 1234, - SolidSourceValue = "1234" - } - } - }); - - cache.Add(new SolidPublishedContent(contentType2) - { - Id = 2, - SortOrder = 1, - Name = "Content 2", - UrlSegment = "content-2", - Path = "/2", - Level = 1, - Url = "/content-2", - ParentId = -1, - ChildIds = new int[] { }, - Properties = new Collection - { - new SolidPublishedProperty - { - Alias = "prop1", - SolidHasValue = true, - SolidValue = 1234, - SolidSourceValue = "1234" - } - } - }); - - cache.Add(new SolidPublishedContent(contentType2Sub) - { - Id = 3, - SortOrder = 2, - Name = "Content 2Sub", - UrlSegment = "content-2sub", - Path = "/3", - Level = 1, - Url = "/content-2sub", - ParentId = -1, - ChildIds = new int[] { }, - Properties = new Collection - { - new SolidPublishedProperty - { - Alias = "prop1", - SolidHasValue = true, - SolidValue = 1234, - SolidSourceValue = "1234" - } - } - }); - - return caches; - } } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs new file mode 100644 index 0000000000..dc88f8bea1 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs @@ -0,0 +1,100 @@ +using System; +using System.Linq; +using System.Collections.ObjectModel; +using System.Web.Routing; +using Moq; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; +using Umbraco.Web.Security; +using Umbraco.Core.Composing; +using Current = Umbraco.Core.Composing.Current; +using LightInject; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing.Objects.Accessors; + +namespace Umbraco.Tests.PublishedContent +{ + public abstract class PublishedContentSnapshotTestBase : PublishedContentTestBase + { + // read http://stackoverflow.com/questions/7713326/extension-method-that-works-on-ienumerablet-and-iqueryablet + // and http://msmvps.com/blogs/jon_skeet/archive/2010/10/28/overloading-and-generic-constraints.aspx + // and http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx + + public override void SetUp() + { + base.SetUp(); + + var umbracoContext = GetUmbracoContext(); + Umbraco.Web.Composing.Current.UmbracoContextAccessor.UmbracoContext = umbracoContext; + } + + protected override void Compose() + { + base.Compose(); + + Container.RegisterSingleton(f => new PublishedModelFactory(f.GetInstance().GetTypes())); + } + + protected override TypeLoader CreatePluginManager(IContainer f) + { + var pluginManager = base.CreatePluginManager(f); + + // this is so the model factory looks into the test assembly + pluginManager.AssembliesToScan = pluginManager.AssembliesToScan + .Union(new[] { typeof (PublishedContentMoreTests).Assembly }) + .ToList(); + + return pluginManager; + } + + private UmbracoContext GetUmbracoContext() + { + RouteData routeData = null; + + var publishedSnapshot = CreatePublishedSnapshot(); + + var publishedSnapshotService = new Mock(); + publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(publishedSnapshot); + + var globalSettings = TestObjects.GetGlobalSettings(); + + var httpContext = GetHttpContextFactory("http://umbraco.local/", routeData).HttpContext; + var umbracoContext = new UmbracoContext( + httpContext, + publishedSnapshotService.Object, + new WebSecurity(httpContext, Current.Services.UserService, globalSettings), + TestObjects.GetUmbracoSettings(), + Enumerable.Empty(), + globalSettings, + new TestVariationContextAccessor()); + + return umbracoContext; + } + + public override void TearDown() + { + base.TearDown(); + + Current.Reset(); + } + + private SolidPublishedSnapshot CreatePublishedSnapshot() + { + var dataTypeService = new TestObjects.TestDataTypeService( + new DataType(new VoidEditor(Mock.Of())) { Id = 1 }); + + var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); + var caches = new SolidPublishedSnapshot(); + var cache = caches.InnerContentCache; + PopulateCache(factory, cache); + return caches; + } + + internal abstract void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache); + } +} diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 2a8bc8a8b2..6eef72bcd9 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -37,7 +37,7 @@ namespace Umbraco.Tests.PublishedContent var logger = Mock.Of(); var dataTypeService = new TestObjects.TestDataTypeService( - new DataType(new VoidEditor(logger)) { Id = 1}, + new DataType(new VoidEditor(logger)) { Id = 1 }, new DataType(new TrueFalsePropertyEditor(logger)) { Id = 1001 }, new DataType(new RichTextPropertyEditor(logger)) { Id = 1002 }, new DataType(new IntegerPropertyEditor(logger)) { Id = 1003 }, @@ -337,11 +337,11 @@ namespace Umbraco.Tests.PublishedContent } [Test] - public void GetPropertyValueRecursiveTest() + public void Get_Property_Value_Recursive() { var doc = GetNode(1174); - var rVal = doc.Value("testRecursive", recurse: true); - var nullVal = doc.Value("DoNotFindThis", recurse: true); + var rVal = doc.Value("testRecursive", fallback: Fallback.ToAncestors); + var nullVal = doc.Value("DoNotFindThis", fallback: Fallback.ToAncestors); Assert.AreEqual("This is the recursive val", rVal); Assert.AreEqual(null, nullVal); } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs index df7e1d9d09..0e360bae58 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs @@ -40,21 +40,6 @@ namespace Umbraco.Tests.PublishedContent Assert.IsFalse(result); } - [Test] - public void ConfigureRequest_Adds_HttpContext_Items_When_Published_Content_Assigned() - { - var umbracoContext = GetUmbracoContext("/test"); - var publishedRouter = CreatePublishedRouter(); - var request = publishedRouter.CreateRequest(umbracoContext); - var content = GetPublishedContentMock(); - request.PublishedContent = content.Object; - request.Culture = new CultureInfo("en-AU"); - publishedRouter.ConfigureRequest(request); - - Assert.AreEqual(1, umbracoContext.HttpContext.Items["pageID"]); - Assert.AreEqual(request.UmbracoPage.Elements.Count, ((Hashtable) umbracoContext.HttpContext.Items["pageElements"]).Count); - } - [Test] public void ConfigureRequest_Sets_UmbracoPage_When_Published_Content_Assigned() { diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index 756b775e46..efd1c6ae8b 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -248,7 +248,7 @@ namespace Umbraco.Tests.PublishedContent #endregion } - class SolidPublishedProperty : IPublishedProperty + internal class SolidPublishedProperty : IPublishedProperty { public PublishedPropertyType PropertyType { get; set; } public string Alias { get; set; } @@ -257,10 +257,86 @@ namespace Umbraco.Tests.PublishedContent public bool SolidHasValue { get; set; } public object SolidXPathValue { get; set; } - public object GetSourceValue(string culture = null, string segment = null) => SolidSourceValue; - public object GetValue(string culture = null, string segment = null) => SolidValue; - public object GetXPathValue(string culture = null, string segment = null) => SolidXPathValue; - public bool HasValue(string culture = null, string segment = null) => SolidHasValue; + public virtual object GetSourceValue(string culture = null, string segment = null) => SolidSourceValue; + public virtual object GetValue(string culture = null, string segment = null) => SolidValue; + public virtual object GetXPathValue(string culture = null, string segment = null) => SolidXPathValue; + public virtual bool HasValue(string culture = null, string segment = null) => SolidHasValue; + } + + internal class SolidPublishedPropertyWithLanguageVariants : SolidPublishedProperty + { + private readonly IDictionary _solidSourceValues = new Dictionary(); + private readonly IDictionary _solidValues = new Dictionary(); + private readonly IDictionary _solidXPathValues = new Dictionary(); + + public override object GetSourceValue(string culture = null, string segment = null) + { + if (string.IsNullOrEmpty(culture)) + { + return base.GetSourceValue(culture, segment); + } + + return _solidSourceValues.ContainsKey(culture) ? _solidSourceValues[culture] : null; + } + + public override object GetValue(string culture = null, string segment = null) + { + if (string.IsNullOrEmpty(culture)) + { + return base.GetValue(culture, segment); + } + + return _solidValues.ContainsKey(culture) ? _solidValues[culture] : null; + } + + public override object GetXPathValue(string culture = null, string segment = null) + { + if (string.IsNullOrEmpty(culture)) + { + return base.GetXPathValue(culture, segment); + } + + return _solidXPathValues.ContainsKey(culture) ? _solidXPathValues[culture] : null; + } + + public override bool HasValue(string culture = null, string segment = null) + { + if (string.IsNullOrEmpty(culture)) + { + return base.HasValue(culture, segment); + } + + return _solidSourceValues.ContainsKey(culture); + } + + public void SetSourceValue(string culture, object value, bool defaultValue = false) + { + _solidSourceValues.Add(culture, value); + if (defaultValue) + { + SolidSourceValue = value; + SolidHasValue = true; + } + } + + public void SetValue(string culture, object value, bool defaultValue = false) + { + _solidValues.Add(culture, value); + if (defaultValue) + { + SolidValue = value; + SolidHasValue = true; + } + } + + public void SetXPathValue(string culture, object value, bool defaultValue = false) + { + _solidXPathValues.Add(culture, value); + if (defaultValue) + { + SolidXPathValue = value; + } + } } [PublishedModel("ContentType2")] diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs index 9d08229563..f652205147 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs @@ -168,7 +168,7 @@ namespace Umbraco.Tests.Scoping [Test] public void SupersededEvents2() { - Test_UnPublished += OnDoThingFail; + Test_Unpublished += OnDoThingFail; Test_Deleted += OnDoThingFail; var contentService = Mock.Of(); @@ -177,7 +177,7 @@ namespace Umbraco.Tests.Scoping var scopeProvider = _testObjects.GetScopeProvider(Mock.Of()); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { - scope.Events.Dispatch(Test_UnPublished, contentService, new PublishEventArgs(new [] { content }), "UnPublished"); + scope.Events.Dispatch(Test_Unpublished, contentService, new PublishEventArgs(new [] { content }), "Unpublished"); scope.Events.Dispatch(Test_Deleted, contentService, new DeleteEventArgs(new [] { content }), "Deleted"); // see U4-10764 @@ -394,7 +394,7 @@ namespace Umbraco.Tests.Scoping public static event TypedEventHandler> DoThing3; - public static event TypedEventHandler> Test_UnPublished; + public static event TypedEventHandler> Test_Unpublished; public static event TypedEventHandler> Test_Deleted; public class TestEventArgs : CancellableObjectEventArgs diff --git a/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs b/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs index 11ef8a9411..23e5e472a3 100644 --- a/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs +++ b/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs @@ -14,7 +14,7 @@ using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; -using Umbraco.Web.Security.Identity; + namespace Umbraco.Tests.Security { diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 2f1cb74e80..cde161132a 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1205,7 +1205,7 @@ namespace Umbraco.Tests.Services } [Test] - public void Can_UnPublish_Content() + public void Can_Unpublish_Content() { // Arrange var contentService = ServiceContext.ContentService; @@ -2486,7 +2486,7 @@ namespace Umbraco.Tests.Services { var languageService = ServiceContext.LocalizationService; - var langUk = new Language("en-UK") { IsDefaultVariantLanguage = true }; + var langUk = new Language("en-UK") { IsDefault = true }; var langFr = new Language("fr-FR"); languageService.Save(langFr); @@ -2521,7 +2521,7 @@ namespace Umbraco.Tests.Services { var languageService = ServiceContext.LocalizationService; - var langUk = new Language("en-UK") { IsDefaultVariantLanguage = true }; + var langUk = new Language("en-UK") { IsDefault = true }; var langFr = new Language("fr-FR"); languageService.Save(langFr); @@ -2559,7 +2559,7 @@ namespace Umbraco.Tests.Services var languageService = ServiceContext.LocalizationService; //var langFr = new Language("fr-FR") { IsDefaultVariantLanguage = true }; - var langXx = new Language("pt-PT") { IsDefaultVariantLanguage = true }; + var langXx = new Language("pt-PT") { IsDefault = true }; var langFr = new Language("fr-FR"); var langUk = new Language("en-UK"); var langDe = new Language("de-DE"); diff --git a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs index 033fa08d4a..a61b208c4d 100644 --- a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs +++ b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs @@ -192,6 +192,20 @@ namespace Umbraco.Tests.Services Assert.Null(language); } + [Test] + public void Can_Delete_Language_Used_As_Fallback() + { + var danish = ServiceContext.LocalizationService.GetLanguageByIsoCode("da-DK"); + var norwegian = new Language("nb-NO") { CultureName = "Norwegian", FallbackLanguageId = danish.Id }; + ServiceContext.LocalizationService.Save(norwegian, 0); + var languageId = danish.Id; + + ServiceContext.LocalizationService.Delete(danish); + + var language = ServiceContext.LocalizationService.GetLanguageById(languageId); + Assert.Null(language); + } + [Test] public void Can_Create_DictionaryItem_At_Root() { @@ -362,21 +376,21 @@ namespace Umbraco.Tests.Services { var localizationService = ServiceContext.LocalizationService; var language = new Core.Models.Language("en-AU"); - language.IsDefaultVariantLanguage = true; + language.IsDefault = true; localizationService.Save(language); var result = localizationService.GetLanguageById(language.Id); - Assert.IsTrue(result.IsDefaultVariantLanguage); + Assert.IsTrue(result.IsDefault); var language2 = new Core.Models.Language("en-NZ"); - language2.IsDefaultVariantLanguage = true; + language2.IsDefault = true; localizationService.Save(language2); var result2 = localizationService.GetLanguageById(language2.Id); //re-get result = localizationService.GetLanguageById(language.Id); - Assert.IsTrue(result2.IsDefaultVariantLanguage); - Assert.IsFalse(result.IsDefaultVariantLanguage); + Assert.IsTrue(result2.IsDefault); + Assert.IsFalse(result.IsDefault); } [Test] diff --git a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs index 08e91071cb..81dec809c8 100644 --- a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -3,9 +3,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; +using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -30,6 +32,14 @@ namespace Umbraco.Tests.Strings Assert.IsInstanceOf(helper); } + [TestCase("hello-world.png", "Hello World")] + [TestCase("hello-world .png", "Hello World")] + [TestCase("_hello-world __1.png", "Hello World 1")] + public void To_Friendly_Name(string first, string second) + { + Assert.AreEqual(first.ToFriendlyName(), second); + } + [TestCase("hello", "world", false)] [TestCase("hello", "hello", true)] [TestCase("hellohellohellohellohellohellohello", "hellohellohellohellohellohellohelloo", false)] diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 6ea7fa58e2..7b1e47798a 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -89,7 +89,11 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting var httpContext = Mock.Of( http => http.User == owinContext.Authentication.User //ensure the request exists with a cookies collection - && http.Request == Mock.Of(r => r.Cookies == new HttpCookieCollection()) + && http.Request == Mock.Of(r => r.Cookies == new HttpCookieCollection() + && r.RequestContext == new System.Web.Routing.RequestContext + { + RouteData = new System.Web.Routing.RouteData() + }) //ensure the request exists with an items collection && http.Items == httpContextItems); //chuck it into the props since this is what MS does when hosted and it's needed there diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs index 64a22926a0..5834415568 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs @@ -58,14 +58,14 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting var response = await server.HttpClient.SendAsync(request); Console.WriteLine(response); - var json = ""; if (response.IsSuccessStatusCode == false) { WriteResponseError(response); } - else + + var json = (await ((StreamContent)response.Content).ReadAsStringAsync()).TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); + if (!json.IsNullOrWhiteSpace()) { - json = (await ((StreamContent) response.Content).ReadAsStringAsync()).TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); var deserialized = JsonConvert.DeserializeObject(json); Console.Write(JsonConvert.SerializeObject(deserialized, Formatting.Indented)); } diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs index cc9418cc50..0a0d381570 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs @@ -77,7 +77,6 @@ namespace Umbraco.Tests.TestHelpers MockService(), MockService(), MockService(), - MockService(), MockService()); } diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 61860e29aa..ed3bee5c45 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -119,7 +119,6 @@ namespace Umbraco.Tests.TestHelpers var externalLoginService = GetLazyService(container, c => new ExternalLoginService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var publicAccessService = GetLazyService(container, c => new PublicAccessService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var taskService = GetLazyService(container, c => new TaskService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); var domainService = GetLazyService(container, c => new DomainService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var auditService = GetLazyService(container, c => new AuditService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); @@ -189,7 +188,6 @@ namespace Umbraco.Tests.TestHelpers return new ServiceContext( publicAccessService, - taskService, domainService, auditService, localizedTextService, diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 5951a87d77..26c4c1e785 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -35,6 +35,7 @@ using Umbraco.Web; using Umbraco.Web.Services; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web.Composing.Composers; +using Umbraco.Web.ContentApps; using Umbraco.Web._Legacy.Actions; using Current = Umbraco.Core.Composing.Current; using Umbraco.Web.Routing; @@ -168,6 +169,11 @@ namespace Umbraco.Tests.Testing Container.RegisterSingleton(f => new SerilogLogger(new FileInfo(TestHelper.MapPathForTest("~/unit-test.config")))); Container.RegisterSingleton(f => new LogProfiler(f.GetInstance())); } + else if (option == UmbracoTestOptions.Logger.Console) + { + Container.RegisterSingleton(f => new ConsoleLogger()); + Container.RegisterSingleton(f => new LogProfiler(f.GetInstance())); + } Container.RegisterSingleton(f => new ProfilingLogger(f.GetInstance(), f.GetInstance())); } @@ -203,6 +209,9 @@ namespace Umbraco.Tests.Testing Container.RegisterSingleton(); Container.RegisterSingleton(); + + // register empty content apps collection + Container.RegisterCollectionBuilder(); } protected virtual void ComposeCacheHelper() diff --git a/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs b/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs index deefd33946..5248026788 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs @@ -7,7 +7,9 @@ // pure mocks Mock, // Serilog for tests - Serilog + Serilog, + // console logger + Console } public enum Database diff --git a/src/Umbraco.Tests/UI/LegacyDialogTests.cs b/src/Umbraco.Tests/UI/LegacyDialogTests.cs index ffbca1e0e8..ba7c4f0e66 100644 --- a/src/Umbraco.Tests/UI/LegacyDialogTests.cs +++ b/src/Umbraco.Tests/UI/LegacyDialogTests.cs @@ -25,8 +25,8 @@ namespace Umbraco.Tests.UI [TestCase(typeof(MemberGroupTasks), Constants.Applications.Members)] [TestCase(typeof(dictionaryTasks), Constants.Applications.Settings)] - [TestCase(typeof(macroTasks), Constants.Applications.Developer)] - [TestCase(typeof(CreatedPackageTasks), Constants.Applications.Developer)] + [TestCase(typeof(macroTasks), Constants.Applications.Packages)] + [TestCase(typeof(CreatedPackageTasks), Constants.Applications.Packages)] public void Check_Assigned_Apps_For_Tasks(Type taskType, string app) { var task = (LegacyDialogTask)Activator.CreateInstance(taskType); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 19e894cd56..e11695bef5 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -78,11 +78,14 @@ + + 1.8.9 + - + @@ -96,7 +99,7 @@ - + @@ -121,6 +124,8 @@ + + @@ -223,8 +228,6 @@ - - @@ -259,8 +262,6 @@ - - diff --git a/src/Umbraco.Tests/Web/AngularIntegration/JsInitializationTests.cs b/src/Umbraco.Tests/Web/AngularIntegration/JsInitializationTests.cs index dacba39163..f16abd578a 100644 --- a/src/Umbraco.Tests/Web/AngularIntegration/JsInitializationTests.cs +++ b/src/Umbraco.Tests/Web/AngularIntegration/JsInitializationTests.cs @@ -19,15 +19,17 @@ namespace Umbraco.Tests.Web.AngularIntegration [Test] public void Parse_Main() { - var result = JsInitialization.WriteScript(new[] {"[World]", "Hello" }); + var result = JsInitialization.WriteScript("[World]", "Hello", "Blah"); Assert.AreEqual(@"LazyLoad.js([World], function () { //we need to set the legacy UmbClientMgr path - UmbClientMgr.setUmbracoPath('Hello'); + if ((typeof UmbClientMgr) !== ""undefined"") { + UmbClientMgr.setUmbracoPath('Hello'); + } jQuery(document).ready(function () { - angular.bootstrap(document, ['umbraco']); + angular.bootstrap(document, ['Blah']); }); });".StripWhitespace(), result.StripWhitespace()); diff --git a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs index a74b8e3869..4089f9a426 100644 --- a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs @@ -24,6 +24,12 @@ using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.PublishedCache; using Umbraco.Web._Legacy.Actions; using Task = System.Threading.Tasks.Task; +using Umbraco.Core.Dictionary; +using Umbraco.Web.PropertyEditors; +using System; +using Umbraco.Web.WebApi; +using Umbraco.Web.Trees; +using System.Globalization; namespace Umbraco.Tests.Web.Controllers { @@ -40,6 +46,8 @@ namespace Umbraco.Tests.Web.Controllers var userServiceMock = new Mock(); userServiceMock.Setup(service => service.GetUserById(It.IsAny())) .Returns((int id) => id == 1234 ? new User(1234, "Test", "test@test.com", "test@test.com", "", new List(), new int[0], new int[0]) : null); + userServiceMock.Setup(x => x.GetProfileById(It.IsAny())) + .Returns((int id) => id == 1234 ? new User(1234, "Test", "test@test.com", "test@test.com", "", new List(), new int[0], new int[0]) : null); userServiceMock.Setup(service => service.GetPermissionsForPath(It.IsAny(), It.IsAny())) .Returns(new EntityPermissionSet(123, new EntityPermissionCollection(new[] { @@ -54,23 +62,34 @@ namespace Umbraco.Tests.Web.Controllers var entityService = new Mock(); entityService.Setup(x => x.GetAllPaths(UmbracoObjectTypes.Document, It.IsAny())) - .Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath {Path = $"-1,{x}", Id = x}).ToList()); + .Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath { Path = $"-1,{x}", Id = x }).ToList()); var dataTypeService = new Mock(); dataTypeService.Setup(service => service.GetDataType(It.IsAny())) - .Returns(MockedDataType()); + .Returns(Mock.Of(type => type.Id == 9876 && type.Name == "text")); + dataTypeService.Setup(service => service.GetDataType(-87)) //the RTE + .Returns(Mock.Of(type => type.Id == -87 && type.Name == "Rich text" && type.Configuration == new RichTextConfiguration())); + + var langService = new Mock(); + langService.Setup(x => x.GetAllLanguages()).Returns(new[] { + Mock.Of(x => x.IsoCode == "en-US"), + Mock.Of(x => x.IsoCode == "es-ES"), + Mock.Of(x => x.IsoCode == "fr-FR") + }); + + var textService = new Mock(); + textService.Setup(x => x.Localize(It.IsAny(), It.IsAny(), It.IsAny>())).Returns(""); Container.RegisterSingleton(f => Mock.Of()); Container.RegisterSingleton(f => userServiceMock.Object); Container.RegisterSingleton(f => entityService.Object); Container.RegisterSingleton(f => dataTypeService.Object); + Container.RegisterSingleton(f => langService.Object); + Container.RegisterSingleton(f => textService.Object); + Container.RegisterSingleton(f => Mock.Of()); + Container.RegisterSingleton(f => new UmbracoApiControllerTypeCollection(new[] { typeof(ContentTreeController) })); } - private IDataType MockedDataType() - { - return Mock.Of(type => type.Id == 9876 && type.Name == "text"); - } - private MultipartFormDataContent GetMultiPartRequestContent(string json) { var multiPartBoundary = "----WebKitFormBoundary123456789"; @@ -89,14 +108,34 @@ namespace Umbraco.Tests.Web.Controllers }; } - private const string PublishJson1 = @"{ + private IContent GetMockedContent() + { + var content = MockedContent.CreateSimpleContent(MockedContentTypes.CreateSimpleContentType()); + content.Id = 123; + content.Path = "-1,123"; + //ensure things have ids + var ids = 888; + foreach (var g in content.PropertyGroups) + { + g.Id = ids; + ids++; + } + foreach (var p in content.PropertyTypes) + { + p.Id = ids; + ids++; + } + return content; + } + + private const string PublishJsonInvariant = @"{ ""id"": 123, ""contentTypeAlias"": ""page"", ""parentId"": -1, ""action"": ""save"", ""variants"": [ { - ""name"": null, + ""name"": ""asdf"", ""properties"": [ { ""id"": 1, @@ -104,10 +143,34 @@ namespace Umbraco.Tests.Web.Controllers ""value"": ""asdf"" } ], - ""culture"": ""en-US"" + ""culture"": null, + ""save"": true, + ""publish"": true + } + ] +}"; + + private const string PublishJsonVariant = @"{ + ""id"": 123, + ""contentTypeAlias"": ""page"", + ""parentId"": -1, + ""action"": ""save"", + ""variants"": [ + { + ""name"": ""asdf"", + ""properties"": [ + { + ""id"": 1, + ""alias"": ""title"", + ""value"": ""asdf"" + } + ], + ""culture"": ""en-US"", + ""save"": true, + ""publish"": true }, { - ""name"": null, + ""name"": ""asdf"", ""properties"": [ { ""id"": 1, @@ -115,7 +178,9 @@ namespace Umbraco.Tests.Web.Controllers ""value"": ""asdf"" } ], - ""culture"": ""fr-FR"" + ""culture"": ""fr-FR"", + ""save"": true, + ""publish"": true }, { ""name"": ""asdf"", @@ -142,12 +207,8 @@ namespace Umbraco.Tests.Web.Controllers { ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) { - //var content = MockedContent.CreateSimpleContent(MockedContentTypes.CreateSimpleContentType()); - //content.Id = 999999999; //this will not be found - //content.Path = "-1,999999999"; - var contentServiceMock = Mock.Get(Current.Services.ContentService); - contentServiceMock.Setup(x => x.GetById(123)).Returns(() => null); + contentServiceMock.Setup(x => x.GetById(123)).Returns(() => null); //do not find it var publishedSnapshot = Mock.Of(); var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); @@ -157,7 +218,7 @@ namespace Umbraco.Tests.Web.Controllers var runner = new TestRunner(Factory); var response = await runner.Execute("Content", "PostSave", HttpMethod.Post, - content: GetMultiPartRequestContent(PublishJson1), + content: GetMultiPartRequestContent(PublishJsonInvariant), mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"), assertOkResponse: false); @@ -174,7 +235,7 @@ namespace Umbraco.Tests.Web.Controllers ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) { var contentServiceMock = Mock.Get(Current.Services.ContentService); - contentServiceMock.Setup(x => x.GetById(123)).Returns(() => null); + contentServiceMock.Setup(x => x.GetById(123)).Returns(() => GetMockedContent()); var publishedSnapshot = Mock.Of(); var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); @@ -182,9 +243,9 @@ namespace Umbraco.Tests.Web.Controllers return usersController; } - var json = JsonConvert.DeserializeObject(PublishJson1); + var json = JsonConvert.DeserializeObject(PublishJsonInvariant); //remove all save flaggs - ((JArray)json["variants"])[2]["save"] = false; + ((JArray)json["variants"])[0]["save"] = false; var runner = new TestRunner(Factory); var response = await runner.Execute("Content", "PostSave", HttpMethod.Post, @@ -200,20 +261,51 @@ namespace Umbraco.Tests.Web.Controllers /// Returns 404 if any of the posted properties dont actually exist ///
/// - [Test, Ignore("Not implemented yet")] + [Test] public async Task PostSave_Validate_Properties_Exist() { - //TODO: Make this work! to finish it, we need to include a property in the POST data that doesn't exist on the content type - // or change the content type below to not include one of the posted ones + ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) + { + var contentServiceMock = Mock.Get(Current.Services.ContentService); + contentServiceMock.Setup(x => x.GetById(123)).Returns(() => GetMockedContent()); + + var publishedSnapshot = Mock.Of(); + var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); + var usersController = new ContentController(publishedSnapshot, propertyEditorCollection); + return usersController; + } + + var json = JsonConvert.DeserializeObject(PublishJsonInvariant); + //add a non-existent property to a variant being saved + var variantProps = (JArray)json["variants"].ElementAt(0)["properties"]; + variantProps.Add(JObject.FromObject(new + { + id = 2, + alias = "doesntExist", + value = "hello" + })); + + var runner = new TestRunner(Factory); + var response = await runner.Execute("Content", "PostSave", HttpMethod.Post, + content: GetMultiPartRequestContent(JsonConvert.SerializeObject(json)), + mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"), + assertOkResponse: false); + + Assert.AreEqual(HttpStatusCode.NotFound, response.Item1.StatusCode); + } + + [Test] + public async Task PostSave_Simple_Invariant() + { + var content = GetMockedContent(); ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) { - var content = MockedContent.CreateSimpleContent(MockedContentTypes.CreateSimpleContentType()); - content.Id = 123; - content.Path = "-1,123"; var contentServiceMock = Mock.Get(Current.Services.ContentService); - contentServiceMock.Setup(x => x.GetById(123)).Returns(() => null); + contentServiceMock.Setup(x => x.GetById(123)).Returns(() => content); + contentServiceMock.Setup(x => x.Save(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(new OperationResult(OperationResultType.Success, new Core.Events.EventMessages())); //success var publishedSnapshot = Mock.Of(); var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); @@ -223,14 +315,90 @@ namespace Umbraco.Tests.Web.Controllers var runner = new TestRunner(Factory); var response = await runner.Execute("Content", "PostSave", HttpMethod.Post, - content: GetMultiPartRequestContent(PublishJson1), + content: GetMultiPartRequestContent(PublishJsonInvariant), mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"), assertOkResponse: false); - Assert.AreEqual(HttpStatusCode.NotFound, response.Item1.StatusCode); - - //var obj = JsonConvert.DeserializeObject>(response.Item2); - //Assert.AreEqual(0, obj.TotalItems); + Assert.AreEqual(HttpStatusCode.OK, response.Item1.StatusCode); + var display = JsonConvert.DeserializeObject(response.Item2); + Assert.AreEqual(1, display.Variants.Count()); + Assert.AreEqual(content.PropertyGroups.Count(), display.Variants.ElementAt(0).Tabs.Count()); + Assert.AreEqual(content.PropertyTypes.Count(), display.Variants.ElementAt(0).Tabs.ElementAt(0).Properties.Count()); } + + [Test] + public async Task PostSave_Validate_Empty_Name() + { + var content = GetMockedContent(); + + ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) + { + + var contentServiceMock = Mock.Get(Current.Services.ContentService); + contentServiceMock.Setup(x => x.GetById(123)).Returns(() => content); + contentServiceMock.Setup(x => x.Save(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(new OperationResult(OperationResultType.Success, new Core.Events.EventMessages())); //success + + var publishedSnapshot = Mock.Of(); + var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); + var usersController = new ContentController(publishedSnapshot, propertyEditorCollection); + return usersController; + } + + //clear out the name + var json = JsonConvert.DeserializeObject(PublishJsonInvariant); + json["variants"].ElementAt(0)["name"] = null; + + var runner = new TestRunner(Factory); + var response = await runner.Execute("Content", "PostSave", HttpMethod.Post, + content: GetMultiPartRequestContent(JsonConvert.SerializeObject(json)), + mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"), + assertOkResponse: false); + + Assert.AreEqual(HttpStatusCode.BadRequest, response.Item1.StatusCode); + var display = JsonConvert.DeserializeObject(response.Item2); + Assert.AreEqual(1, display.Errors.Count()); + Assert.IsTrue(display.Errors.ContainsKey("Variants[0].Name")); + //ModelState":{"Variants[0].Name":["Required"]} + } + + [Test] + public async Task PostSave_Validate_Variants_Empty_Name() + { + var content = GetMockedContent(); + + ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) + { + + var contentServiceMock = Mock.Get(Current.Services.ContentService); + contentServiceMock.Setup(x => x.GetById(123)).Returns(() => content); + contentServiceMock.Setup(x => x.Save(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(new OperationResult(OperationResultType.Success, new Core.Events.EventMessages())); //success + + var publishedSnapshot = Mock.Of(); + var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); + var usersController = new ContentController(publishedSnapshot, propertyEditorCollection); + return usersController; + } + + //clear out one of the names + var json = JsonConvert.DeserializeObject(PublishJsonVariant); + json["variants"].ElementAt(0)["name"] = null; + + var runner = new TestRunner(Factory); + var response = await runner.Execute("Content", "PostSave", HttpMethod.Post, + content: GetMultiPartRequestContent(JsonConvert.SerializeObject(json)), + mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"), + assertOkResponse: false); + + Assert.AreEqual(HttpStatusCode.BadRequest, response.Item1.StatusCode); + var display = JsonConvert.DeserializeObject(response.Item2); + Assert.AreEqual(2, display.Errors.Count()); + Assert.IsTrue(display.Errors.ContainsKey("Variants[0].Name")); + Assert.IsTrue(display.Errors.ContainsKey("_content_variant_en-US_")); + } + + //TODO: There are SOOOOO many more tests we should write - a lot of them to do with validation + } } diff --git a/src/Umbraco.Tests/Web/Mvc/HtmlStringUtilitiesTests.cs b/src/Umbraco.Tests/Web/Mvc/HtmlStringUtilitiesTests.cs index 102a310806..04bd4f6e15 100644 --- a/src/Umbraco.Tests/Web/Mvc/HtmlStringUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/HtmlStringUtilitiesTests.cs @@ -18,7 +18,7 @@ namespace Umbraco.Tests.Web.Mvc [Test] public void ReplaceLineBreaksWithHtmlBreak() { - var output = _htmlStringUtilities.ReplaceLineBreaksForHtml("

hello world

hello world\r\nhello world\rhello world\nhello world

"); + var output = _htmlStringUtilities.ReplaceLineBreaksForHtml("

hello world

hello world\r\nhello world\rhello world\nhello world

").ToString(); var expected = "

hello world

hello world
hello world
hello world
hello world

"; Assert.AreEqual(expected, output); } @@ -58,4 +58,4 @@ namespace Umbraco.Tests.Web.Mvc } -} \ No newline at end of file +} diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index e60a4d9397..db496dbfd3 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -1,6 +1,6 @@ using System; -using System.Linq; using System.Web; +using HtmlAgilityPack; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -10,7 +10,6 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; @@ -18,6 +17,8 @@ using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; using Umbraco.Web.Templates; +using System.Linq; +using Umbraco.Core.Services; namespace Umbraco.Tests.Web { @@ -59,6 +60,14 @@ namespace Umbraco.Tests.Web [TestCase("hello href=\"{localLink:umb://document-type/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")] //this one has an invalid char so won't match [TestCase("hello href=\"{localLink:umb^://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")] + // with a-tag with data-udi attribute, that needs to be stripped + [TestCase("hello world ", "hello world ")] + // with a-tag with data-udi attribute spelled wrong, so don't need stripping + [TestCase("hello world ", "hello world ")] + // with a img-tag with data-udi id, that needs to be strippde + [TestCase("hello world ", "hello world ")] + // with a img-tag with data-udi id spelled wrong, so don't need stripping + [TestCase("hello world ", "hello world ")] public void ParseLocalLinks(string input, string result) { var serviceCtxMock = new TestObjects(null).GetServiceContextMock(); @@ -99,7 +108,7 @@ namespace Umbraco.Tests.Web //setup a quick mock of the WebRouting section Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "AutoLegacy")), //pass in the custom url provider - new[]{ testUrlProvider.Object }, + new[] { testUrlProvider.Object }, globalSettings, new TestVariationContextAccessor(), true)) @@ -109,5 +118,27 @@ namespace Umbraco.Tests.Web Assert.AreEqual(result, output); } } + + [Test] + public void StripDataUdiAttributesUsingSrtringOnLinks() + { + var input = "hello world "; + var expected = "hello world "; + + var result = TemplateUtilities.StripUdiDataAttributes(input); + + Assert.AreEqual(expected, result); + } + + [Test] + public void StripDataUdiAttributesUsingStringOnImages() + { + var input = "hello world "; + var expected = "hello world "; + + var result = TemplateUtilities.StripUdiDataAttributes(input); + + Assert.AreEqual(expected, result); + } } } diff --git a/src/Umbraco.Web.UI.Client/.babelrc b/src/Umbraco.Web.UI.Client/.babelrc new file mode 100644 index 0000000000..ff3059c3f0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env"] +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/.eslintrc b/src/Umbraco.Web.UI.Client/.eslintrc index 1b39098cfc..1bfa081b9a 100644 --- a/src/Umbraco.Web.UI.Client/.eslintrc +++ b/src/Umbraco.Web.UI.Client/.eslintrc @@ -7,6 +7,10 @@ "comma-dangle": ["error", "never"] }, + "parserOptions": { + "ecmaVersion": 6 + }, + "globals": { "angular": false, "_": false, diff --git a/src/Umbraco.Web.UI.Client/.jshintignore b/src/Umbraco.Web.UI.Client/.jshintignore deleted file mode 100644 index 97620d1820..0000000000 --- a/src/Umbraco.Web.UI.Client/.jshintignore +++ /dev/null @@ -1 +0,0 @@ -src/common/services/util.service.js \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index 1d7096cb29..9f9c4fff01 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -1,98 +1,100 @@ { - "name": "umbraco", - "version": "7", - "homepage": "https://github.com/umbraco/Umbraco-CMS", - "authors": [ - "Shannon " - ], - "description": "Umbraco CMS", - "license": "MIT", - "private": true, - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], - "dependencies": { - "angular": "~1.7.2", - "angular-cookies": "~1.7.2", - "angular-sanitize": "~1.7.2", - "angular-touch": "~1.7.2", - "angular-route": "~1.7.2", - "angular-animate": "~1.7.2", - "angular-i18n": "~1.7.2", - "signalr": "^2.2.1", - "typeahead.js": "~0.10.5", - "underscore": "~1.9.1", - "rgrove-lazyload": "*", - "bootstrap-social": "~4.8.0", - "jquery": "2.2.4", - "jquery-ui": "~1.12.0", - "jquery-migrate": "1.4.0", - "jquery-validate": "~1.17.0", - "jquery-validation-unobtrusive": "3.2.10", - "angular-dynamic-locale": "~0.1.36", - "ng-file-upload": "~12.2.13", - "tinymce": "~4.7.1", - "codemirror": "~5.3.0", - "angular-local-storage": "~0.7.1", - "moment": "~2.10.3", - "ace-builds": "~1.3.0", - "clipboard": "~2.0.0", - "font-awesome": "~4.2", - "animejs": "^2.2.0", - "angular-ui-sortable": "0.14.4", - "angular-messages": "^1.7.2" - }, - "install": { - "path": "lib-bower", - "ignore": [ - "font-awesome", - "bootstrap", - "codemirror", - "ace-builds" + "name": "umbraco", + "version": "7", + "homepage": "https://github.com/umbraco/Umbraco-CMS", + "authors": [ + "Shannon " ], - "sources": { - "moment": [ - "bower_components/moment/min/moment.min.js", - "bower_components/moment/min/moment-with-locales.js", - "bower_components/moment/min/moment-with-locales.min.js", - "bower_components/moment/locale/*.js" + "description": "Umbraco CMS", + "license": "MIT", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "angular": "~1.7.4", + "angular-cookies": "~1.7.4", + "angular-sanitize": "~1.7.4", + "angular-touch": "~1.7.4", + "angular-route": "~1.7.4", + "angular-animate": "~1.7.4", + "angular-i18n": "~1.7.4", + "signalr": "^2.2.1", + "typeahead.js": "~0.10.5", + "underscore": "~1.9.1", + "rgrove-lazyload": "*", + "bootstrap-social": "~4.8.0", + "jquery": "2.2.4", + "jquery-ui": "~1.12.0", + "jquery-migrate": "1.4.0", + "jquery-validate": "~1.17.0", + "jquery-validation-unobtrusive": "3.2.10", + "angular-dynamic-locale": "~0.1.36", + "ng-file-upload": "~12.2.13", + "tinymce": "~4.7.1", + "codemirror": "~5.3.0", + "angular-local-storage": "~0.7.1", + "moment": "~2.10.3", + "ace-builds": "~1.3.0", + "clipboard": "~2.0.0", + "font-awesome": "~4.2", + "animejs": "^2.2.0", + "angular-ui-sortable": "0.14.4", + "angular-messages": "^1.7.2", + "jsdiff": "^3.4.0" + }, + "install": { + "path": "lib-bower", + "ignore": [ + "font-awesome", + "bootstrap", + "codemirror", + "ace-builds" ], - "underscore": [ - "bower_components/underscore/underscore-min.js", - "bower_components/underscore/underscore-min.map" - ], - "jquery": [ - "bower_components/jquery/dist/jquery.min.js", - "bower_components/jquery/dist/jquery.min.map" - ], - "angular-dynamic-locale": [ - "bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js", - "bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js.map" - ], - "angular-local-storage": [ - "bower_components/angular-local-storage/dist/angular-local-storage.min.js", - "bower_components/angular-local-storage/dist/angular-local-storage.min.js.map" - ], - "tinymce": [ - "bower_components/tinymce/tinymce.min.js" - ], - "angular-i18n": "bower_components/angular-i18n/angular-locale_*.js", - "typeahead.js": "bower_components/typeahead.js/dist/typeahead.bundle.min.js", - "rgrove-lazyload": "bower_components/rgrove-lazyload/lazyload.js", - "ng-file-upload": "bower_components/ng-file-upload/ng-file-upload.min.js", - "jquery-ui": "bower_components/jquery-ui/jquery-ui.min.js", - "jquery-migrate": "bower_components/jquery-migrate/jquery-migrate.min.js", - "clipboard": "bower_components/clipboard/dist/clipboard.min.js", - "animejs": "bower_components/animejs/anime.min.js", - "jquery-validate": "bower_components/jquery-validate/dist/jquery.validate.min.js", - "jquery-validation-unobtrusive": "bower_components/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js" + "sources": { + "moment": [ + "bower_components/moment/min/moment.min.js", + "bower_components/moment/min/moment-with-locales.js", + "bower_components/moment/min/moment-with-locales.min.js", + "bower_components/moment/locale/*.js" + ], + "underscore": [ + "bower_components/underscore/underscore-min.js", + "bower_components/underscore/underscore-min.map" + ], + "jquery": [ + "bower_components/jquery/dist/jquery.min.js", + "bower_components/jquery/dist/jquery.min.map" + ], + "angular-dynamic-locale": [ + "bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js", + "bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js.map" + ], + "angular-local-storage": [ + "bower_components/angular-local-storage/dist/angular-local-storage.min.js", + "bower_components/angular-local-storage/dist/angular-local-storage.min.js.map" + ], + "tinymce": [ + "bower_components/tinymce/tinymce.min.js" + ], + "angular-i18n": "bower_components/angular-i18n/angular-locale_*.js", + "typeahead.js": "bower_components/typeahead.js/dist/typeahead.bundle.min.js", + "rgrove-lazyload": "bower_components/rgrove-lazyload/lazyload.js", + "ng-file-upload": "bower_components/ng-file-upload/ng-file-upload.min.js", + "jquery-ui": "bower_components/jquery-ui/jquery-ui.min.js", + "jquery-migrate": "bower_components/jquery-migrate/jquery-migrate.min.js", + "clipboard": "bower_components/clipboard/dist/clipboard.min.js", + "animejs": "bower_components/animejs/anime.min.js", + "jquery-validate": "bower_components/jquery-validate/dist/jquery.validate.min.js", + "jquery-validation-unobtrusive": "bower_components/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js", + "jsdiff": "bower_components/jsdiff/diff.min.js" + } + }, + "devDependencies": { + "angular-mocks": "~1.7.2" } - }, - "devDependencies": { - "angular-mocks": "~1.7.2" - } } diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 33bc80f1ef..7a6d909154 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -6,6 +6,7 @@ var wrap = require("gulp-wrap-js"); var sort = require('gulp-sort'); var connect = require('gulp-connect'); var open = require('gulp-open'); +const babel = require("gulp-babel"); var runSequence = require('run-sequence'); const imagemin = require('gulp-imagemin'); @@ -33,17 +34,18 @@ Helper functions function processJs(files, out) { return gulp.src(files) - // check for js errors - .pipe(eslint()) - // outputs the lint results to the console - .pipe(eslint.format()) - // sort files in stream by path or any custom sort comparator - .pipe(sort()) - .pipe(concat(out)) - .pipe(wrap('(function(){\n%= body %\n})();')) - .pipe(gulp.dest(root + targets.js)); + // check for js errors + .pipe(eslint()) + // outputs the lint results to the console + .pipe(eslint.format()) + // sort files in stream by path or any custom sort comparator + .pipe(babel()) + .pipe(sort()) + .pipe(concat(out)) + .pipe(wrap('(function(){\n%= body %\n})();')) + .pipe(gulp.dest(root + targets.js)); - console.log(out + " compiled"); + console.log(out + " compiled"); } function processLess(files, out) { @@ -80,7 +82,7 @@ var sources = { //js files for backoffie //processed in the js task js: { - preview: { files: ["src/canvasdesigner/**/*.js"], out: "umbraco.canvasdesigner.js" }, + preview: { files: ["src/preview/**/*.js"], out: "umbraco.preview.js" }, installer: { files: ["src/installer/**/*.js"], out: "umbraco.installer.js" }, controllers: { files: ["src/{views,controllers}/**/*.controller.js"], out: "umbraco.controllers.js" }, directives: { files: ["src/common/directives/**/*.js"], out: "umbraco.directives.js" }, @@ -124,7 +126,7 @@ var targets = { // Build - build the files ready for production gulp.task('build', function(cb) { - runSequence(["dependencies", "js", "less", "views"], "test:unit", cb); + runSequence(["dependencies", "js", "less", "views"], cb); }); // Dev - build the files ready for development and start watchers @@ -142,7 +144,7 @@ gulp.task('docserve', function(cb) { **************************/ gulp.task('dependencies', function () { - //bower component specific copy rules + //bower component/npm specific copy rules //this is to patch the sometimes wonky rules these libs are distrbuted under //as we do multiple things in this task, we merge the multiple streams @@ -199,6 +201,16 @@ gulp.task('dependencies', function () { .pipe(gulp.dest(root + targets.lib + "/codemirror")) ); + // npm dependencies + // flatpickr + stream.add( + gulp.src([ + "./node_modules/flatpickr/dist/flatpickr.js", + "./node_modules/flatpickr/dist/flatpickr.css"], + { base: "./node_modules/flatpickr/dist" }) + .pipe(gulp.dest(root + targets.lib + "/flatpickr")) + ); + //copy over libs which are not on bower (/lib) and //libraries that have been managed by bower-installer (/lib-bower) stream.add( diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.min.css b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.min.css index c17e10b917..6fa010bce0 100755 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.min.css +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/skins/umbraco/skin.min.css @@ -1 +1 @@ -.mce-container,.mce-container *,.mce-widget,.mce-widget *{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:#000;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;-webkit-tap-highlight-color:transparent;line-height:normal}.mce-container *[unselectable]{-moz-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.mce-container ::-webkit-scrollbar{width:8px;height:8px;-webkit-border-radius:4px}.mce-container ::-webkit-scrollbar-track,.mce-container ::-webkit-scrollbar-track-piece{background-color:transparent}.mce-container ::-webkit-scrollbar-thumb{background-color:rgba(53,57,71,0.3);width:6px;height:6px;-webkit-border-radius:4px}.mce-fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.mce-fade.mce-in{opacity:1}.mce-tinymce{visibility:visible!important;position:relative}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%;z-index:100}div.mce-fullscreen{position:fixed;top:0;left:0;width:100%;height:auto}.mce-tinymce{display:block;border-radius:2px}.mce-wordcount{float:right;padding:8px}.mce-edit-area{background:#FFF;filter:none}.mce-statusbar{position:relative}.mce-statusbar .mce-container-body{position:relative}.mce-fullscreen .mce-resizehandle{display:none}.mce-charmap{border-collapse:collapse}.mce-charmap td{cursor:default;border:1px solid #c5c5c5;width:20px;height:20px;line-height:20px;text-align:center;vertical-align:center;padding:2px}.mce-charmap td:hover{background:#d9d9d9}.mce-grid{border-spacing:2px;border-collapse:separate}.mce-grid a{display:block;border:1px solid transparent}.mce-grid a:hover{border-color:#c5c5c5}.mce-grid-border{margin:0 4px 0 4px}.mce-grid-border a{border-color:#e8e8e8;width:13px;height:13px}.mce-grid-border a:hover,.mce-grid-border a.mce-active{border-color:#c4daff;background:#deeafa}.mce-text-center{text-align:center}div.mce-tinymce-inline{width:100%;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.mce-container,.mce-container-body{display:block}.mce-autoscroll{overflow:hidden}.mce-scrollbar{position:absolute;width:7px;height:100%;top:2px;right:2px;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-scrollbar-h{top:auto;right:auto;left:2px;bottom:2px;width:100%;height:7px}.mce-scrollbar-thumb{position:absolute;background-color:#000;border:1px solid #888;border-color:rgba(85,85,85,0.6);width:5px;height:100%;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scrollbar-h .mce-scrollbar-thumb{width:100%;height:5px}.mce-scrollbar:hover,.mce-scrollbar.mce-active{background-color:#AAA;opacity:.6;filter:alpha(opacity=60);zoom:1;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scroll{position:relative}.mce-panel{border:0 solid #9e9e9e;background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}.mce-floatpanel{position:absolute;-webkit-box-shadow:#ccc 5px 5px 5px;-moz-box-shadow:#ccc 5px 5px 5px;box-shadow:#ccc 5px 5px 5px}.mce-floatpanel .mce-arrow,.mce-floatpanel .mce-arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.mce-floatpanel .mce-arrow{border-width:11px}.mce-floatpanel .mce-arrow:after{border-width:10px;content:""}.mce-floatpanel.mce-popover{position:absolute;top:0;left:0;background:#fff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-floatpanel.mce-popover.mce-bottom{margin-top:10px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.mce-floatpanel.mce-popover.mce-bottom.mce-start{margin-left:-22px}.mce-floatpanel.mce-popover.mce-bottom.mce-start>.mce-arrow{left:20px}.mce-floatpanel.mce-popover.mce-bottom.mce-end{margin-left:22px}.mce-floatpanel.mce-popover.mce-bottom.mce-end>.mce-arrow{right:10px;left:auto}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%}div.mce-fullscreen{position:fixed;top:0;left:0}#mce-modal-block{opacity:0;filter:alpha(opacity=0);zoom:1;position:fixed;left:0;top:0;width:100%;height:100%;background:#000}#mce-modal-block.mce-in{opacity:.3;filter:alpha(opacity=30);zoom:1}.mce-window-move{cursor:move}.mce-window{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;background:#FFF;position:fixed;top:0;left:0;opacity:0;-webkit-transition:opacity 150ms ease-in;transition:opacity 150ms ease-in}.mce-window.mce-in{opacity:1}.mce-window-head{padding:9px 15px;border-bottom:1px solid #EEE;position:relative}.mce-window-head .mce-close{position:absolute;right:15px;top:9px;font-size:20px;font-weight:bold;line-height:20px;color:#CCC;text-shadow:0 1px 0 white;cursor:pointer;height:20px;overflow:hidden}.mce-close:hover{color:#AAA}.mce-window-head .mce-title{display:inline-block;*display:inline;*zoom:1;line-height:20px;font-size:20px;font-weight:bold;text-rendering:optimizelegibility;padding-right:10px}.mce-window .mce-container-body{display:block}.mce-foot{display:block;background-color:whiteSmoke;border-top:1px solid #DDD;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.mce-window-head .mce-dragh{position:absolute;top:0;left:0;cursor:move;width:90%;height:100%}.mce-window iframe{width:100%;height:100%}.mce-window.mce-fullscreen,.mce-window.mce-fullscreen .mce-foot{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-abs-layout{position:relative}body .mce-abs-layout-item,.mce-abs-end{position:absolute}.mce-abs-end{width:1px;height:1px}.mce-container-body.mce-abs-layout{overflow:hidden}.mce-tooltip{position:absolute;padding:5px;opacity:.8;filter:alpha(opacity=80);zoom:1}.mce-tooltip-inner{font-size:11px;background-color:#000;color:#fff;max-width:200px;padding:5px 8px 4px 8px;text-align:center;white-space:normal}.mce-tooltip-inner{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-tooltip-inner{-webkit-box-shadow:0 0 5px #000;-moz-box-shadow:0 0 5px #000;box-shadow:0 0 5px #000}.mce-tooltip-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed #000}.mce-tooltip-arrow-n{border-bottom-color:#000}.mce-tooltip-arrow-s{border-top-color:#000}.mce-tooltip-arrow-e{border-left-color:#000}.mce-tooltip-arrow-w{border-right-color:#000}.mce-tooltip-n .mce-tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-nw .mce-tooltip-arrow{top:0;left:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-ne .mce-tooltip-arrow{top:0;right:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-s .mce-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-sw .mce-tooltip-arrow{bottom:0;left:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-se .mce-tooltip-arrow{bottom:0;right:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-e .mce-tooltip-arrow{right:0;top:50%;margin-top:-5px;border-left-style:solid;border-right:0;border-top-color:transparent;border-bottom-color:transparent}.mce-tooltip-w .mce-tooltip-arrow{left:0;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:transparent;border-bottom-color:transparent}.mce-btn{border:1px solid #c5c5c5;position:relative;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-btn:hover,.mce-btn:focus{text-decoration:none;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn.mce-disabled,.mce-btn.mce-disabled:hover{cursor:default;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn.mce-active,.mce-btn.mce-active:hover{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn button{padding:4px 10px;font-size:14px;line-height:20px;*line-height:16px;cursor:pointer;color:#333;overflow:visible;-webkit-appearance:none}.mce-btn button::-moz-focus-inner{border:0;padding:0}.mce-btn i{text-shadow:1px 1px #fff}.mce-primary{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);zoom:1;border-color:#04c #04c #002b80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary:hover,.mce-primary:focus{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#005fb3;background-image:-moz-linear-gradient(top,#0077b3,#003cb3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0077b3),to(#003cb3));background-image:-webkit-linear-gradient(top,#0077b3,#003cb3);background-image:-o-linear-gradient(top,#0077b3,#003cb3);background-image:linear-gradient(to bottom,#0077b3,#003cb3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0077b3',endColorstr='#ff003cb3',GradientType=0);zoom:1;border-color:#003cb3 #003cb3 #026;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary button{color:#333}.mce-btn-large button{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.mce-btn-large i{margin-top:2px}.mce-btn-small button{padding:3px 5px;font-size:12px;line-height:15px}.mce-btn-small i{margin-top:0}.mce-btn .mce-caret{margin-top:8px;*margin-top:6px;margin-left:0}.mce-btn-small .mce-caret{margin-top:6px;*margin-top:4px;margin-left:0}.mce-caret{display:inline-block;*display:inline;*zoom:1;width:0;height:0;vertical-align:top;border-top:4px solid #444;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.mce-disabled .mce-caret{border-top-color:#999}.mce-caret.mce-up{border-bottom:4px solid #444;border-top:0}.mce-btn-group .mce-btn{border-width:1px 0 1px 0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-btn-group .mce-btn:hover,.mce-btn-group .mce-btn:focus{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-disabled,.mce-btn-group .mce-btn.mce-disabled:hover{-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-active,.mce-btn-group .mce-btn.mce-active:hover,.mce-btn-group .mce-btn:active{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn-group .mce-btn.mce-disabled button{opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn-group .mce-first{border-left:1px solid #c5c5c5;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.mce-btn-group .mce-last{border-right:1px solid #c5c5c5;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.mce-btn-group .mce-first.mce-last{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-btn-group .mce-btn.mce-flow-layout-item{margin:0}.mce-checkbox{cursor:pointer}i.mce-i-checkbox{margin:0 3px 0 0;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1;text-indent:-10em;*font-size:0;*line-height:0;*text-indent:0}.mce-checked i.mce-i-checkbox{color:#000;font-size:16px;line-height:16px;text-indent:0}.mce-checkbox:focus i.mce-i-checkbox{border:1px solid #59a5e1;border:1px solid rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-colorbutton .mce-ico{position:relative}.mce-colorpicker{background:#FFF}.mce-colorbutton-grid{margin:4px}.mce-grid td div{border:1px solid #808080;width:12px;height:12px;margin:2px;cursor:pointer}.mce-grid td div:hover{border-color:black}.mce-grid td div:focus{border-color:#59a5e1;outline:1px solid rgba(82,168,236,0.8);border-color:rgba(82,168,236,0.8)}.mce-colorbutton{position:relative}.mce-colorbutton .mce-preview{display:block;position:absolute;left:50%;top:50%;margin-left:-8px;margin-top:7px;background:gray;width:16px;height:2px;overflow:hidden}.mce-combobox{display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;width:100px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-combobox input{border-color:1px solid #c5c5c5;border-right-color:rgba(0,0,0,0.15);height:28px}.mce-combobox.mce-has-open input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.mce-combobox .mce-btn{border-left:0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.mce-combobox button{padding-right:8px;padding-left:8px}.mce-combobox *:focus{border-color:#59a5e1;border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-path{display:inline-block;*display:inline;*zoom:1;padding:8px;white-space:normal}.mce-path .mce-txt{display:inline-block;padding-right:3px}.mce-path .mce-path-body{display:inline-block}.mce-path-item{display:inline-block;*display:inline;*zoom:1;cursor:pointer;color:#000}.mce-path-item:hover{text-decoration:underline}.mce-path-item:focus{background:gray;color:white}.mce-path .mce-divider{display:inline}.mce-fieldset{border:0 solid #9e9e9e;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-fieldset>.mce-container-body{margin-top:-15px}.mce-fieldset-title{margin-left:5px;padding:0 5px 0 5px}.mce-fit-layout{display:inline-block;*display:inline;*zoom:1}.mce-fit-layout-item{position:absolute}.mce-flow-layout-item{display:inline-block;*display:inline;*zoom:1}.mce-flow-layout-item{margin:2px 0 2px 2px}.mce-flow-layout-item.mce-last{margin-right:2px}.mce-flow-layout{white-space:normal}.mce-iframe{border:0 solid #c5c5c5;width:100%;height:100%}.mce-label{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);border:0 solid #c5c5c5;overflow:hidden}.mce-label.mce-autoscroll{overflow:auto}.mce-label-disabled .mce-text{color:#999}.mce-label.mce-multiline{white-space:pre-wrap}.mce-menubar .mce-menubtn{border-color:transparent;background:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:none}.mce-menubar{border:1px solid #ddd}.mce-menubar .mce-menubtn button{color:#000}.mce-menubar .mce-menubtn:hover,.mce-menubar .mce-menubtn.mce-active,.mce-menubtn:focus{border-color:transparent;background:#ddd;filter:none}.mce-menubtn.mce-disabled span{color:#999}.mce-listbox button{text-align:left;padding-right:20px;position:relative}.mce-listbox .mce-caret{position:absolute;margin-top:-2px;right:8px;top:50%}.mce-listbox span{width:100%;display:block;overflow:hidden}.mce-menu-item{display:block;padding:6px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap;cursor:pointer;line-height:normal}.mce-menu-item.mce-disabled .mce-text{color:#999}.mce-menu-item:hover,.mce-menu-item.mce-selected,.mce-menu-item:focus{text-decoration:none;color:#fff;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0);zoom:1}.mce-menu-item:hover .mce-text,.mce-menu-item.mce-selected .mce-text{color:#fff}.mce-menu-item:hover .mce-ico,.mce-menu-item.mce-selected .mce-ico,.mce-menu-item:focus .mce-ico{color:white}.mce-menu-shortcut{display:inline-block;color:#999}.mce-menu-shortcut{display:inline-block;*display:inline;*zoom:1;padding:0 20px 0 20px}.mce-menu-item .mce-caret{margin-top:6px;*margin-top:3px;margin-right:6px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #666}.mce-menu-item.mce-selected .mce-caret,.mce-menu-item:focus .mce-caret{border-left-color:#FFF}.mce-menu-align .mce-menu-shortcut{*margin-top:-2px}.mce-menu-align .mce-menu-shortcut,.mce-menu-align .mce-caret{position:absolute;right:0}.mce-menu-item-sep,.mce-menu-item-sep:hover{padding:0;height:1px;margin:9px 1px;overflow:hidden;background:#e5e5e5;border-bottom:1px solid white;cursor:default;filter:none}.mce-menu-item.mce-active i{visibility:visible}.mce-menu-item.mce-active{background-color:#c8def4;outline:1px solid #c5c5c5}.mce-menu-item-checkbox.mce-active{background-color:#FFF;outline:0}.mce-menu{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;z-index:1000;padding:5px 0 5px 0;margin:2px 0 0;min-width:160px;background:#FFF;border:1px solid #CCC;border:1px solid rgba(0,0,0,0.2);z-index:1002;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-menu i{display:none}.mce-menu-has-icons i{display:inline-block;*display:inline;*zoom:1}.mce-menu-sub{margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}i.mce-radio{padding:1px;margin:0 3px 0 0;background-color:#fafafa;border:1px solid #cacece;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}i.mce-radio:after{font-family:Arial;font-size:12px;color:#000;content:'\25cf'}.mce-container-body .mce-resizehandle{position:absolute;right:0;bottom:0;width:16px;height:16px;visibility:visible;cursor:s-resize;margin:0}.mce-resizehandle-both{cursor:se-resize}i.mce-i-resize{color:#000}.mce-spacer{visibility:hidden}.mce-splitbtn .mce-open{border-left:1px solid transparent;border-right:1px solid transparent}.mce-splitbtn:hover .mce-open{border-left-color:#c5c5c5;border-right-color:#c5c5c5}.mce-splitbtn button{padding-right:4px}.mce-splitbtn .mce-open{padding-left:4px}.mce-splitbtn .mce-open.mce-active{-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-stack-layout-item{display:block}.mce-tabs{display:block;border-bottom:1px solid #ccc}.mce-tab{display:inline-block;*display:inline;*zoom:1;border:1px solid #ccc;border-width:1px 1px 0 0;background:#e3e3e3;padding:8px;text-shadow:0 1px 1px rgba(255,255,255,0.75);height:13px;cursor:pointer}.mce-tab:hover{background:#fdfdfd}.mce-tab.mce-active{background:#fdfdfd;border-bottom-color:transparent;margin-bottom:-1px;height:14px}.mce-textbox{background:#FFF;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);display:inline-block;-webkit-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s;height:28px;resize:none;padding:0 4px 0 4px;white-space:normal;color:#000}.mce-textbox:focus{border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-placeholder .mce-textbox{color:#aaa}.mce-textbox.mce-multiline{padding:4px}.mce-throbber{position:absolute;top:0;left:0;width:100%;height:100%;opacity:.6;filter:alpha(opacity=60);zoom:1;background:#fff url('img/loader.gif') no-repeat center center}@font-face{font-family:'tinymce';src:url('fonts/tinymce.eot');src:url('fonts/tinymce.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce.woff') format('woff'),url('fonts/tinymce.ttf') format('truetype'),url('fonts/tinymce.svg#tinymce') format('svg');font-weight:normal;font-style:normal}@font-face{font-family:'tinymce-small';src:url('fonts/tinymce-small.eot');src:url('fonts/tinymce-small.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce-small.woff') format('woff'),url('fonts/tinymce-small.ttf') format('truetype'),url('fonts/tinymce-small.svg#tinymce') format('svg');font-weight:normal;font-style:normal}.mce-ico{font-family:'tinymce',Arial;font-style:normal;font-weight:normal;font-size:16px;line-height:16px;vertical-align:text-top;-webkit-font-smoothing:antialiased;display:inline-block;background:transparent center center;width:16px;height:16px;color:#333}.mce-i-save:before{content:"\e000"}.mce-i-newdocument:before{content:"\e001"}.mce-i-fullpage:before{content:"\e002"}.mce-i-alignleft:before{content:"\e003"}.mce-i-aligncenter:before{content:"\e004"}.mce-i-alignright:before{content:"\e005"}.mce-i-alignjustify:before{content:"\e006"}.mce-i-cut:before{content:"\e007"}.mce-i-paste:before{content:"\e008"}.mce-i-searchreplace:before{content:"\e009"}.mce-i-bullist:before{content:"\e00a"}.mce-i-numlist:before{content:"\e00b"}.mce-i-indent:before{content:"\e00c"}.mce-i-outdent:before{content:"\e00d"}.mce-i-blockquote:before{content:"\e00e"}.mce-i-undo:before{content:"\e00f"}.mce-i-redo:before{content:"\e010"}.mce-i-link:before{content:"\e011"}.mce-i-unlink:before{content:"\e012"}.mce-i-anchor:before{content:"\e013"}.mce-i-image:before{content:"\e014"}.mce-i-media:before{content:"\e015"}.mce-i-help:before{content:"\e016"}.mce-i-code:before{content:"\e017"}.mce-i-inserttime:before{content:"\e018"}.mce-i-preview:before{content:"\e019"}.mce-i-forecolor:before{content:"\e01a"}.mce-i-backcolor:before{content:"\e01a"}.mce-i-table:before{content:"\e01b"}.mce-i-hr:before{content:"\e01c"}.mce-i-removeformat:before{content:"\e01d"}.mce-i-subscript:before{content:"\e01e"}.mce-i-superscript:before{content:"\e01f"}.mce-i-charmap:before{content:"\e020"}.mce-i-emoticons:before{content:"\e021"}.mce-i-print:before{content:"\e022"}.mce-i-fullscreen:before{content:"\e023"}.mce-i-spellchecker:before{content:"\e024"}.mce-i-nonbreaking:before{content:"\e025"}.mce-i-template:before{content:"\e026"}.mce-i-pagebreak:before{content:"\e027"}.mce-i-restoredraft:before{content:"\e028"}.mce-i-untitled:before{content:"\e029"}.mce-i-bold:before{content:"\e02a"}.mce-i-italic:before{content:"\e02b"}.mce-i-underline:before{content:"\e02c"}.mce-i-strikethrough:before{content:"\e02d"}.mce-i-visualchars:before{content:"\e02e"}.mce-i-visualblocks:before{content:"\e026"}.mce-i-ltr:before{content:"\e02f"}.mce-i-rtl:before{content:"\e030"}.mce-i-copy:before{content:"\e031"}.mce-i-resize:before{content:"\e032"}.mce-i-browse:before{content:"\e034"}.mce-i-checkbox:before,.mce-i-selected:before{content:"\e033"}.mce-i-selected{visibility:hidden}i.mce-i-backcolor{text-shadow:none;background:#BBB}.mce-i-tablerowprops:before{content:"\e604"}.mce-i-tablecellprops:before{content:"\e605"}.mce-i-table2:before{content:"\e606"}.mce-i-tablemergecells:before{content:"\e607"}.mce-i-tableinsertcolbefore:before{content:"\e608"}.mce-i-tableinsertcolafter:before{content:"\e609"}.mce-i-tableinsertrowbefore:before{content:"\e60a"}.mce-i-tableinsertrowafter:before{content:"\e60b"}.mce-i-tablesplitcells:before{content:"\e60d"}.mce-i-tabledelete:before{content:"\e60e"}.mce-i-tableleftheader:before{content:"\e62a"}.mce-i-tabletopheader:before{content:"\e62b"}.mce-i-tabledeleterow:before{content:"\e800"}.mce-i-tabledeletecol:before{content:"\e801"} \ No newline at end of file +.mce-container,.mce-container *,.mce-widget,.mce-widget *{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:#000;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;-webkit-tap-highlight-color:transparent;line-height:normal}.mce-container *[unselectable]{-moz-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.mce-container ::-webkit-scrollbar{width:8px;height:8px;-webkit-border-radius:4px}.mce-container ::-webkit-scrollbar-track,.mce-container ::-webkit-scrollbar-track-piece{background-color:transparent}.mce-container ::-webkit-scrollbar-thumb{background-color:rgba(53,57,71,0.3);width:6px;height:6px;-webkit-border-radius:4px}.mce-fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.mce-fade.mce-in{opacity:1}.mce-tinymce{visibility:visible!important;position:relative}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%;z-index:100}div.mce-fullscreen{position:fixed;top:0;left:0;width:100%;height:auto}.mce-tinymce{display:block;border-radius:2px}.mce-wordcount{float:right;padding:8px}.mce-edit-area{background:#FFF;filter:none}.mce-statusbar{position:relative}.mce-statusbar .mce-container-body{position:relative}.mce-fullscreen .mce-resizehandle{display:none}.mce-charmap{border-collapse:collapse}.mce-charmap td{cursor:default;border:1px solid #c5c5c5;width:20px;height:20px;line-height:20px;text-align:center;vertical-align:center;padding:2px}.mce-charmap td:hover{background:#d9d9d9}.mce-grid{border-spacing:2px;border-collapse:separate}.mce-grid a{display:block;border:1px solid transparent}.mce-grid a:hover{border-color:#c5c5c5}.mce-grid-border{margin:0 4px 0 4px}.mce-grid-border a{border-color:#e8e8e8;width:13px;height:13px}.mce-grid-border a:hover,.mce-grid-border a.mce-active{border-color:#c4daff;background:#deeafa}.mce-text-center{text-align:center}div.mce-tinymce-inline{width:100%;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.mce-container,.mce-container-body{display:block}.mce-autoscroll{overflow:hidden}.mce-scrollbar{position:absolute;width:7px;height:100%;top:2px;right:2px;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-scrollbar-h{top:auto;right:auto;left:2px;bottom:2px;width:100%;height:7px}.mce-scrollbar-thumb{position:absolute;background-color:#000;border:1px solid #888;border-color:rgba(85,85,85,0.6);width:5px;height:100%;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scrollbar-h .mce-scrollbar-thumb{width:100%;height:5px}.mce-scrollbar:hover,.mce-scrollbar.mce-active{background-color:#AAA;opacity:.6;filter:alpha(opacity=60);zoom:1;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.mce-scroll{position:relative}.mce-panel{border:0 solid #9e9e9e;background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}.mce-floatpanel{position:absolute;-webkit-box-shadow:#ccc 5px 5px 5px;-moz-box-shadow:#ccc 5px 5px 5px;box-shadow:#ccc 5px 5px 5px}.mce-floatpanel .mce-arrow,.mce-floatpanel .mce-arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.mce-floatpanel .mce-arrow{border-width:11px}.mce-floatpanel .mce-arrow:after{border-width:10px;content:""}.mce-floatpanel.mce-popover{position:absolute;top:0;left:0;background:#fff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-floatpanel.mce-popover.mce-bottom{margin-top:10px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.mce-floatpanel.mce-popover.mce-bottom.mce-start{margin-left:-22px}.mce-floatpanel.mce-popover.mce-bottom.mce-start>.mce-arrow{left:20px}.mce-floatpanel.mce-popover.mce-bottom.mce-end{margin-left:22px}.mce-floatpanel.mce-popover.mce-bottom.mce-end>.mce-arrow{right:10px;left:auto}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;background:#FFF;height:100%}div.mce-fullscreen{position:fixed;top:0;left:0}#mce-modal-block{opacity:0;filter:alpha(opacity=0);zoom:1;position:fixed;left:0;top:0;width:100%;height:100%;background:#000}#mce-modal-block.mce-in{opacity:.3;filter:alpha(opacity=30);zoom:1}.mce-window-move{cursor:move}.mce-window{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;background:#FFF;position:fixed;top:0;left:0;opacity:0;-webkit-transition:opacity 150ms ease-in;transition:opacity 150ms ease-in}.mce-window.mce-in{opacity:1}.mce-window-head{padding:9px 15px;border-bottom:1px solid #EEE;position:relative}.mce-window-head .mce-close{position:absolute;right:15px;top:9px;font-size:20px;font-weight:bold;line-height:20px;color:#CCC;text-shadow:0 1px 0 white;cursor:pointer;height:20px;overflow:hidden}.mce-close:hover{color:#AAA}.mce-window-head .mce-title{display:inline-block;*display:inline;*zoom:1;line-height:20px;font-size:20px;font-weight:bold;text-rendering:optimizelegibility;padding-right:10px}.mce-window .mce-container-body{display:block}.mce-foot{display:block;background-color:whiteSmoke;border-top:1px solid #DDD;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.mce-window-head .mce-dragh{position:absolute;top:0;left:0;cursor:move;width:90%;height:100%}.mce-window iframe{width:100%;height:100%}.mce-window.mce-fullscreen,.mce-window.mce-fullscreen .mce-foot{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-abs-layout{position:relative}body .mce-abs-layout-item,.mce-abs-end{position:absolute}.mce-abs-end{width:1px;height:1px}.mce-container-body.mce-abs-layout{overflow:hidden}.mce-tooltip{position:absolute;padding:5px;opacity:.8;filter:alpha(opacity=80);zoom:1}.mce-tooltip-inner{font-size:11px;background-color:#000;color:#fff;max-width:200px;padding:5px 8px 4px 8px;text-align:center;white-space:normal}.mce-tooltip-inner{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-tooltip-inner{-webkit-box-shadow:0 0 5px #000;-moz-box-shadow:0 0 5px #000;box-shadow:0 0 5px #000}.mce-tooltip-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed #000}.mce-tooltip-arrow-n{border-bottom-color:#000}.mce-tooltip-arrow-s{border-top-color:#000}.mce-tooltip-arrow-e{border-left-color:#000}.mce-tooltip-arrow-w{border-right-color:#000}.mce-tooltip-n .mce-tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-nw .mce-tooltip-arrow{top:0;left:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-ne .mce-tooltip-arrow{top:0;right:10px;border-bottom-style:solid;border-top:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-s .mce-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-sw .mce-tooltip-arrow{bottom:0;left:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-se .mce-tooltip-arrow{bottom:0;right:10px;border-top-style:solid;border-bottom:0;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-e .mce-tooltip-arrow{right:0;top:50%;margin-top:-5px;border-left-style:solid;border-right:0;border-top-color:transparent;border-bottom-color:transparent}.mce-tooltip-w .mce-tooltip-arrow{left:0;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:transparent;border-bottom-color:transparent}.mce-btn{border:1px solid #c5c5c5;position:relative;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-btn:hover,.mce-btn:focus{text-decoration:none;color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn.mce-disabled,.mce-btn.mce-disabled:hover{cursor:default;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn.mce-active,.mce-btn.mce-active:hover{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn button{padding:4px 10px;font-size:14px;line-height:20px;*line-height:16px;cursor:pointer;color:#333;overflow:visible;-webkit-appearance:none}.mce-btn button::-moz-focus-inner{border:0;padding:0}.mce-btn i{text-shadow:1px 1px #fff}.mce-primary{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);zoom:1;border-color:#04c #04c #002b80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary:hover,.mce-primary:focus{color:#fff;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#005fb3;background-image:-moz-linear-gradient(top,#0077b3,#003cb3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0077b3),to(#003cb3));background-image:-webkit-linear-gradient(top,#0077b3,#003cb3);background-image:-o-linear-gradient(top,#0077b3,#003cb3);background-image:linear-gradient(to bottom,#0077b3,#003cb3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0077b3',endColorstr='#ff003cb3',GradientType=0);zoom:1;border-color:#003cb3 #003cb3 #026;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-primary button{color:#333}.mce-btn-large button{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.mce-btn-large i{margin-top:2px}.mce-btn-small button{padding:3px 5px;font-size:12px;line-height:15px}.mce-btn-small i{margin-top:0}.mce-btn .mce-caret{margin-top:8px;*margin-top:6px;margin-left:0}.mce-btn-small .mce-caret{margin-top:6px;*margin-top:4px;margin-left:0}.mce-caret{display:inline-block;*display:inline;*zoom:1;width:0;height:0;vertical-align:top;border-top:4px solid #444;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.mce-disabled .mce-caret{border-top-color:#999}.mce-caret.mce-up{border-bottom:4px solid #444;border-top:0}.mce-btn-group .mce-btn{border-width:1px 0 1px 0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.mce-btn-group .mce-btn:hover,.mce-btn-group .mce-btn:focus{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#e3e3e3;background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#ccc));background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(to bottom,#f2f2f2,#ccc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffcccccc',GradientType=0);zoom:1;border-color:#ccc #ccc #a6a6a6;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-disabled,.mce-btn-group .mce-btn.mce-disabled:hover{-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fff,#d9d9d9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#d9d9d9));background-image:-webkit-linear-gradient(top,#fff,#d9d9d9);background-image:-o-linear-gradient(top,#fff,#d9d9d9);background-image:linear-gradient(to bottom,#fff,#d9d9d9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffd9d9d9',GradientType=0);zoom:1;border-color:#d9d9d9 #d9d9d9 #b3b3b3;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.mce-btn-group .mce-btn.mce-active,.mce-btn-group .mce-btn.mce-active:hover,.mce-btn-group .mce-btn:active{color:#333;text-shadow:0 1px 1px rgba(255,255,255,0.75);background-color:#d6d6d6;background-image:-moz-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf));background-image:-webkit-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:-o-linear-gradient(top,#e6e6e6,#bfbfbf);background-image:linear-gradient(to bottom,#e6e6e6,#bfbfbf);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6',endColorstr='#ffbfbfbf',GradientType=0);zoom:1;border-color:#bfbfbf #bfbfbf #999;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-btn-group .mce-btn.mce-disabled button{opacity:.65;filter:alpha(opacity=65);zoom:1}.mce-btn-group .mce-first{border-left:1px solid #c5c5c5;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.mce-btn-group .mce-last{border-right:1px solid #c5c5c5;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.mce-btn-group .mce-first.mce-last{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-btn-group .mce-btn.mce-flow-layout-item{margin:0}.mce-checkbox{cursor:pointer}i.mce-i-checkbox{margin:0 3px 0 0;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1;text-indent:-10em;*font-size:0;*line-height:0;*text-indent:0}.mce-checked i.mce-i-checkbox{color:#000;font-size:16px;line-height:16px;text-indent:0}.mce-checkbox:focus i.mce-i-checkbox{border:1px solid #59a5e1;border:1px solid rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-colorbutton .mce-ico{position:relative}.mce-colorpicker{background:#FFF}.mce-colorbutton-grid{margin:4px}.mce-grid td div{border:1px solid #808080;width:12px;height:12px;margin:2px;cursor:pointer}.mce-grid td div:hover{border-color:black}.mce-grid td div:focus{border-color:#59a5e1;outline:1px solid rgba(82,168,236,0.8);border-color:rgba(82,168,236,0.8)}.mce-colorbutton{position:relative}.mce-colorbutton .mce-preview{display:block;position:absolute;left:50%;top:50%;margin-left:-8px;margin-top:7px;background:gray;width:16px;height:2px;overflow:hidden}.mce-combobox{display:inline-block;*display:inline;*zoom:1;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;width:100px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.mce-combobox input{border-color:1px solid #c5c5c5;border-right-color:rgba(0,0,0,0.15);height:28px}.mce-combobox.mce-has-open input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.mce-combobox .mce-btn{border-left:0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.mce-combobox button{padding-right:8px;padding-left:8px}.mce-combobox *:focus{border-color:#59a5e1;border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-path{display:inline-block;*display:inline;*zoom:1;padding:8px;white-space:normal}.mce-path .mce-txt{display:inline-block;padding-right:3px}.mce-path .mce-path-body{display:inline-block}.mce-path-item{display:inline-block;*display:inline;*zoom:1;cursor:pointer;color:#000}.mce-path-item:hover{text-decoration:underline}.mce-path-item:focus{background:gray;color:white}.mce-path .mce-divider{display:inline}.mce-fieldset{border:0 solid #9e9e9e;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.mce-fieldset>.mce-container-body{margin-top:-15px}.mce-fieldset-title{margin-left:5px;padding:0 5px 0 5px}.mce-fit-layout{display:inline-block;*display:inline;*zoom:1}.mce-fit-layout-item{position:absolute}.mce-flow-layout-item{display:inline-block;*display:inline;*zoom:1}.mce-flow-layout-item{margin:2px 0 2px 2px}.mce-flow-layout-item.mce-last{margin-right:2px}.mce-flow-layout{white-space:normal}.mce-iframe{border:0 solid #c5c5c5;width:100%;height:100%}.mce-label{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);border:0 solid #c5c5c5;overflow:hidden}.mce-label.mce-autoscroll{overflow:auto}.mce-label-disabled .mce-text{color:#999}.mce-label.mce-multiline{white-space:pre-wrap}.mce-menubar .mce-menubtn{border-color:transparent;background:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:none}.mce-menubar{border:1px solid #ddd}.mce-menubar .mce-menubtn button{color:#000}.mce-menubar .mce-menubtn:hover,.mce-menubar .mce-menubtn.mce-active,.mce-menubtn:focus{border-color:transparent;background:#ddd;filter:none}.mce-menubtn.mce-disabled span{color:#999}.mce-listbox button{text-align:left;padding-right:20px;position:relative}.mce-listbox .mce-caret{position:absolute;margin-top:-2px;right:8px;top:50%}.mce-listbox span{width:100%;display:block;overflow:hidden}.mce-menu-item{display:block;padding:6px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap;cursor:pointer;line-height:normal}.mce-menu-item.mce-disabled .mce-text{color:#999}.mce-menu-item:hover,.mce-menu-item.mce-selected,.mce-menu-item:focus{text-decoration:none;color:#fff;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0);zoom:1}.mce-menu-item:hover .mce-text,.mce-menu-item.mce-selected .mce-text{color:#fff}.mce-menu-item:hover .mce-ico,.mce-menu-item.mce-selected .mce-ico,.mce-menu-item:focus .mce-ico{color:white}.mce-menu-shortcut{display:inline-block;color:#999}.mce-menu-shortcut{display:inline-block;*display:inline;*zoom:1;padding:0 20px 0 20px}.mce-menu-item .mce-caret{margin-top:6px;*margin-top:3px;margin-right:6px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #666}.mce-menu-item.mce-selected .mce-caret,.mce-menu-item:focus .mce-caret{border-left-color:#FFF}.mce-menu-align .mce-menu-shortcut{*margin-top:-2px}.mce-menu-align .mce-menu-shortcut,.mce-menu-align .mce-caret{position:absolute;right:0}.mce-menu-item-sep,.mce-menu-item-sep:hover{padding:0;height:1px;margin:9px 1px;overflow:hidden;background:#e5e5e5;border-bottom:1px solid white;cursor:default;filter:none}.mce-menu-item.mce-active i{visibility:visible}.mce-menu-item.mce-active{background-color:#c8def4;outline:1px solid #c5c5c5}.mce-menu-item-checkbox.mce-active{background-color:#FFF;outline:0}.mce-menu{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;z-index:1000;padding:5px 0 5px 0;margin:2px 0 0;min-width:160px;background:#FFF;border:1px solid #CCC;border:1px solid rgba(0,0,0,0.2);z-index:1002;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.mce-menu i{display:none}.mce-menu-has-icons i{display:inline-block;*display:inline;*zoom:1}.mce-menu-sub{margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}i.mce-radio{padding:1px;margin:0 3px 0 0;background-color:#fafafa;border:1px solid #cacece;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#fdfdfd,#ddd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdfdfd),to(#ddd));background-image:-webkit-linear-gradient(top,#fdfdfd,#ddd);background-image:-o-linear-gradient(top,#fdfdfd,#ddd);background-image:linear-gradient(to bottom,#fdfdfd,#ddd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffdfdfd',endColorstr='#ffdddddd',GradientType=0);zoom:1}i.mce-radio:after{font-family:Arial;font-size:12px;color:#000;content:'\25cf'}.mce-container-body .mce-resizehandle{position:absolute;right:0;bottom:0;width:16px;height:16px;visibility:visible;cursor:s-resize;margin:0}.mce-resizehandle-both{cursor:se-resize}i.mce-i-resize{color:#000}.mce-spacer{visibility:hidden}.mce-splitbtn .mce-open{border-left:1px solid transparent;border-right:1px solid transparent}.mce-splitbtn:hover .mce-open{border-left-color:#c5c5c5;border-right-color:#c5c5c5}.mce-splitbtn button{padding-right:4px}.mce-splitbtn .mce-open{padding-left:4px}.mce-splitbtn .mce-open.mce-active{-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.mce-stack-layout-item{display:block}.mce-tabs{display:block;border-bottom:1px solid #ccc}.mce-tab{display:inline-block;*display:inline;*zoom:1;border:1px solid #ccc;border-width:1px 1px 0 0;background:#e3e3e3;padding:8px;text-shadow:0 1px 1px rgba(255,255,255,0.75);height:13px;cursor:pointer}.mce-tab:hover{background:#fdfdfd}.mce-tab.mce-active{background:#fdfdfd;border-bottom-color:transparent;margin-bottom:-1px;height:14px}.mce-textbox{background:#FFF;border:1px solid #c5c5c5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);display:inline-block;-webkit-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s;height:28px;resize:none;padding:0 4px 0 4px;white-space:normal;color:#000}.mce-textbox:focus{border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.mce-placeholder .mce-textbox{color:#aaa}.mce-textbox.mce-multiline{padding:4px}.mce-throbber{position:absolute;top:0;left:0;width:100%;height:100%;opacity:.6;filter:alpha(opacity=60);zoom:1;background:#fff url('img/loader.gif') no-repeat center center}@font-face{font-family:'tinymce';src:url('fonts/tinymce.eot');src:url('fonts/tinymce.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce.woff') format('woff'),url('fonts/tinymce.ttf') format('truetype'),url('fonts/tinymce.svg#tinymce') format('svg');font-weight:normal;font-style:normal}@font-face{font-family:'tinymce-small';src:url('fonts/tinymce-small.eot');src:url('fonts/tinymce-small.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce-small.woff') format('woff'),url('fonts/tinymce-small.ttf') format('truetype'),url('fonts/tinymce-small.svg#tinymce') format('svg');font-weight:normal;font-style:normal}.mce-ico{font-family:'tinymce',Arial;font-style:normal;font-weight:normal;font-size:16px;line-height:16px;vertical-align:text-top;-webkit-font-smoothing:antialiased;display:inline-block;background:transparent center center;width:16px;height:16px;color:#333}.mce-i-save:before{content:"\e000"}.mce-i-newdocument:before{content:"\e001"}.mce-i-fullpage:before{content:"\e002"}.mce-i-alignleft:before{content:"\e003"}.mce-i-aligncenter:before{content:"\e004"}.mce-i-alignright:before{content:"\e005"}.mce-i-alignjustify:before{content:"\e006"}.mce-i-cut:before{content:"\e007"}.mce-i-paste:before{content:"\e008"}.mce-i-searchreplace:before{content:"\e009"}.mce-i-bullist:before{content:"\e00a"}.mce-i-numlist:before{content:"\e00b"}.mce-i-indent:before{content:"\e00c"}.mce-i-outdent:before{content:"\e00d"}.mce-i-blockquote:before{content:"\e00e"}.mce-i-undo:before{content:"\e00f"}.mce-i-redo:before{content:"\e010"}.mce-i-link:before{content:"\e011"}.mce-i-unlink:before{content:"\e012"}.mce-i-anchor:before{content:"\e013"}.mce-i-image:before{content:"\e014"}.mce-i-media:before{content:"\e015"}.mce-i-help:before{content:"\e016"}.mce-i-code:before{content:"\e017"}.mce-i-inserttime:before{content:"\e018"}.mce-i-preview:before{content:"\e019"}.mce-i-forecolor:before{content:"\e01a"}.mce-i-backcolor:before{content:"\e01a"}.mce-i-table:before{content:"\e01b"}.mce-i-hr:before{content:"\e01c"}.mce-i-removeformat:before{content:"\e01d"}.mce-i-subscript:before{content:"\e01e"}.mce-i-superscript:before{content:"\e01f"}.mce-i-charmap:before{content:"\e020"}.mce-i-emoticons:before{content:"\e021"}.mce-i-print:before{content:"\e022"}.mce-i-fullscreen:before{content:"\e023"}.mce-i-spellchecker:before{content:"\e024"}.mce-i-nonbreaking:before{content:"\e025"}.mce-i-template:before{content:"\e026"}.mce-i-pagebreak:before{content:"\e027"}.mce-i-restoredraft:before{content:"\e028"}.mce-i-untitled:before{content:"\e029"}.mce-i-bold:before{content:"\e02a"}.mce-i-italic:before{content:"\e02b"}.mce-i-underline:before{content:"\e02c"}.mce-i-strikethrough:before{content:"\e02d"}.mce-i-visualchars:before{content:"\e02e"}.mce-i-visualblocks:before{content:"\e026"}.mce-i-ltr:before{content:"\e02f"}.mce-i-rtl:before{content:"\e030"}.mce-i-copy:before{content:"\e031"}.mce-i-resize:before{content:"\e032"}.mce-i-browse:before{content:"\e034"}.mce-i-checkbox:before,.mce-i-selected:before{content:"\e033"}.mce-i-selected{visibility:hidden}i.mce-i-backcolor{text-shadow:none;background:#BBB}.mce-i-tablerowprops:before{content:"\e604"}.mce-i-tablecellprops:before{content:"\e605"}.mce-i-table2:before{content:"\e606"}.mce-i-tablemergecells:before{content:"\e607"}.mce-i-tableinsertcolbefore:before{content:"\e608"}.mce-i-tableinsertcolafter:before{content:"\e609"}.mce-i-tableinsertrowbefore:before{content:"\e60a"}.mce-i-tableinsertrowafter:before{content:"\e60b"}.mce-i-tablesplitcells:before{content:"\e60d"}.mce-i-tabledelete:before{content:"\e60e"}.mce-i-tableleftheader:before{content:"\e62a"}.mce-i-tabletopheader:before{content:"\e62b"}.mce-i-tabledeleterow:before{content:"\e800"}.mce-i-tabledeletecol:before{content:"\e801"}.mce-colorbtn-trans div{text-align: center;vertical-align: middle;font-weight: bold;font-size: 20px;line-height: 16px;color: #707070;}.mce-grid td.mce-grid-cell div{border: 1px solid #d6d6d6;width: 15px;height: 15px;margin: 0;cursor: pointer;} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 2497bdc592..2bbe8d726d 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1,9 +1,812 @@ { - "name": "umbraco", - "version": "0.0.0", - "lockfileVersion": 1, "requires": true, + "lockfileVersion": 1, "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.1.2.tgz", + "integrity": "sha512-IFeSSnjXdhDaoysIlev//UzHZbdEmm7D0EIH2qtse9xK7mXEZQpYjs2P00XlP1qYsYvid79p+Zgg6tz1mp6iVw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.1.2", + "@babel/helpers": "^7.1.2", + "@babel/parser": "^7.1.2", + "@babel/template": "^7.1.2", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.1.2", + "convert-source-map": "^1.1.0", + "debug": "^3.1.0", + "json5": "^0.5.0", + "lodash": "^4.17.10", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.1.2.tgz", + "integrity": "sha512-70A9HWLS/1RHk3Ck8tNHKxOoKQuSKocYgwDN85Pyl/RBduss6AKxUR7RIZ/lzduQMSYfWEM4DDBu6A+XGbkFig==", + "dev": true, + "requires": { + "@babel/types": "^7.1.2", + "jsesc": "^2.5.1", + "lodash": "^4.17.10", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-call-delegate": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz", + "integrity": "sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.0.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-define-map": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz", + "integrity": "sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.0.0", + "lodash": "^4.17.10" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz", + "integrity": "sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", + "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.1.0.tgz", + "integrity": "sha512-0JZRd2yhawo79Rcm4w0LwSMILFmFXjugG3yqf+P/UsKsRS1mJCmMwwlHDlMg7Avr9LrvSpp4ZSULO9r8jpCzcw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0", + "lodash": "^4.17.10" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0.tgz", + "integrity": "sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.1.0.tgz", + "integrity": "sha512-BvcDWYZRWVuDeXTYZWxekQNO5D4kO55aArwZOTFXw6rlLQA8ZaDicJR1sO47h+HrnCiDFiww0fSPV0d713KBGQ==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "dev": true, + "requires": { + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", + "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-wrap-function": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.1.0.tgz", + "integrity": "sha512-R6HU3dete+rwsdAfrOzTlE9Mcpk4RjU3aX3gi9grtmugQY0u79X7eogUvfXA5sI81Mfq1cn6AgxihfN33STjJA==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helpers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.1.2.tgz", + "integrity": "sha512-Myc3pUE8eswD73aWcartxB16K6CGmHDv9KxOmD2CeOs/FaEAQodr3VYGmlvOmog60vNQ2w8QbatuahepZwrHiA==", + "dev": true, + "requires": { + "@babel/template": "^7.1.2", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.1.2" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.1.2.tgz", + "integrity": "sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.1.0.tgz", + "integrity": "sha512-Fq803F3Jcxo20MXUSDdmZZXrPe6BWyGcWBPPNB/M7WaUYESKDeKMOGIxEzQOjGSmW/NWb6UaPZrtTB2ekhB/ew==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.0.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.0.0.tgz", + "integrity": "sha512-kfVdUkIAGJIVmHmtS/40i/fg/AGnw/rsZBCaapY5yjeO5RA9m165Xbw9KMOu2nqXP5dTFjEjHdfNdoVcHv133Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.0.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0.tgz", + "integrity": "sha512-14fhfoPcNu7itSen7Py1iGN0gEm87hX/B+8nZPqkdmANyyYWYMY2pjA3r8WXbWVKMzfnSNS0xY8GVS0IjXi/iw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.0.0" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0.tgz", + "integrity": "sha512-JPqAvLG1s13B/AuoBjdBYvn38RqW6n1TzrQO839/sIpqLpbnXKacsAgpZHzLD83Sm8SDXMkkrAvEnJ25+0yIpw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.0.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.0.0.tgz", + "integrity": "sha512-tM3icA6GhC3ch2SkmSxv7J/hCWKISzwycub6eGsDrFDgukD4dZ/I+x81XgW0YslS6mzNuQ1Cbzh5osjIMgepPQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.2.0" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0.tgz", + "integrity": "sha512-im7ged00ddGKAjcZgewXmp1vxSZQQywuQXe2B1A7kajjZmDeY/ekMPmWr9zJgveSaQH0k7BcGrojQhcK06l0zA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.0.0.tgz", + "integrity": "sha512-UlSfNydC+XLj4bw7ijpldc1uZ/HB84vw+U6BTuqMdIEmz/LDe63w/GHtpQMdXWdqQZFeAI9PjnHe/vDhwirhKA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0.tgz", + "integrity": "sha512-5A0n4p6bIiVe5OvQPxBnesezsgFJdHhSs3uFSvaPdMqtsovajLZ+G2vZyvNe10EzJBWWo3AcHGKhAFUxqwp2dw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0.tgz", + "integrity": "sha512-Wc+HVvwjcq5qBg1w5RG9o9RVzmCaAg/Vp0erHCKpAYV8La6I94o4GQAmFYNmkzoMO6gzoOSulpKeSSz6mPEoZw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0.tgz", + "integrity": "sha512-2EZDBl1WIO/q4DIkIp4s86sdp4ZifL51MoIviLY/gG/mLSuOIEg7J8o6mhbxOTvUJkaN50n+8u41FVsr5KLy/w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.1.0.tgz", + "integrity": "sha512-rNmcmoQ78IrvNCIt/R9U+cixUHeYAzgusTFgIAv+wQb9HJU4szhpDD6e5GCACmj/JP5KxuCwM96bX3L9v4ZN/g==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0.tgz", + "integrity": "sha512-AOBiyUp7vYTqz2Jibe1UaAWL0Hl9JUXEgjFvvvcSc9MVDItv46ViXFw2F7SVt1B5k+KWjl44eeXOAk3UDEaJjQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0.tgz", + "integrity": "sha512-GWEMCrmHQcYWISilUrk9GDqH4enf3UmhOEbNbNrlNAX1ssH3MsS1xLOS6rdjRVPgA7XXVPn87tRkdTEoA/dxEg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.10" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.1.0.tgz", + "integrity": "sha512-rNaqoD+4OCBZjM7VaskladgqnZ1LO6o2UxuWSDzljzW21pN1KXkB7BstAVweZdxQkHAujps5QMNOTWesBciKFg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.1.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0.tgz", + "integrity": "sha512-ubouZdChNAv4AAWAgU7QKbB93NU5sHwInEWfp+/OzJKA02E6Woh9RVoX4sZrbRwtybky/d7baTUqwFx+HgbvMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.1.2.tgz", + "integrity": "sha512-cvToXvp/OsYxtEn57XJu9BvsGSEYjAh9UeUuXpoi7x6QHB7YdWyQ4lRU/q0Fu1IJNT0o0u4FQ1DMQBzJ8/8vZg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0.tgz", + "integrity": "sha512-00THs8eJxOJUFVx1w8i1MBF4XH4PsAjKjQ1eqN/uCH3YKwP21GCKfrn6YZFZswbOk9+0cw1zGQPHVc1KBlSxig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.1.3" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0.tgz", + "integrity": "sha512-w2vfPkMqRkdxx+C71ATLJG30PpwtTpW7DDdLqYt2acXU7YjztzeWW2Jk1T6hKqCLYCcEA5UQM/+xTAm+QCSnuQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.1.0.tgz", + "integrity": "sha512-uZt9kD1Pp/JubkukOGQml9tqAeI8NkE98oZnHZ2qHRElmeKCodbTZgOEUtujSCSLhHSBWbzNiFSDIMC4/RBTLQ==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0.tgz", + "integrity": "sha512-TlxKecN20X2tt2UEr2LNE6aqA0oPeMT1Y3cgz8k4Dn1j5ObT8M3nl9aA37LLklx0PBZKETC9ZAf9n/6SujTuXA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.1.0.tgz", + "integrity": "sha512-VxOa1TMlFMtqPW2IDYZQaHsFrq/dDoIjgN098NowhexhZcz3UGlvPgZXuE1jEvNygyWyxRacqDpCZt+par1FNg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0.tgz", + "integrity": "sha512-1NTDBWkeNXgpUcyoVFxbr9hS57EpZYXpje92zv0SUzjdu3enaRwF/l3cmyRnXLtIdyJASyiS6PtybK+CgKf7jA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.1.0.tgz", + "integrity": "sha512-wt8P+xQ85rrnGNr2x1iV3DW32W8zrB6ctuBkYBbf5/ZzJY99Ob4MFgsZDFgczNU76iy9PWsy4EuxOliDjdKw6A==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.1.0.tgz", + "integrity": "sha512-wtNwtMjn1XGwM0AXPspQgvmE6msSJP15CX2RVfpTSTNPLhKhaOjaIfBaVfj4iUZ/VrFSodcFedwtPg/NxwQlPA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.0.0.tgz", + "integrity": "sha512-8EDKMAsitLkiF/D4Zhe9CHEE2XLh4bfLbb9/Zf3FgXYQOZyZYyg7EAel/aT2A7bHv62jwHf09q2KU/oEexr83g==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.1.0.tgz", + "integrity": "sha512-enrRtn5TfRhMmbRwm7F8qOj0qEYByqUvTttPEGimcBH4CJHphjyK1Vg7sdU7JjeEmgSpM890IT/efS2nMHwYig==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz", + "integrity": "sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.1.0.tgz", + "integrity": "sha512-/O02Je1CRTSk2SSJaq0xjwQ8hG4zhZGNjE8psTsSNPXyLRCODv7/PBozqT5AmQMzp7MI3ndvMhGdqp9c96tTEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.1.0.tgz", + "integrity": "sha512-vHV7oxkEJ8IHxTfRr3hNGzV446GAb+0hgbA7o/0Jd76s+YzccdWuTU296FOCOl/xweU4t/Ya4g41yWz80RFCRw==", + "dev": true, + "requires": { + "@babel/helper-call-delegate": "^7.1.0", + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz", + "integrity": "sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw==", + "dev": true, + "requires": { + "regenerator-transform": "^0.13.3" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0.tgz", + "integrity": "sha512-g/99LI4vm5iOf5r1Gdxq5Xmu91zvjhEG5+yZDJW268AZELAu4J1EiFLnkSG3yuUsZyOipVOVUKoGPYwfsTymhw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0.tgz", + "integrity": "sha512-L702YFy2EvirrR4shTj0g2xQp7aNwZoWNCkNu2mcoU0uyzMl0XRwDSwzB/xp6DSUFiBmEXuyAyEN16LsgVqGGQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0.tgz", + "integrity": "sha512-LFUToxiyS/WD+XEWpkx/XJBrUXKewSZpzX68s+yEOtIbdnsRjpryDw9U06gYc6klYEij/+KQVRnD3nz3AoKmjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0.tgz", + "integrity": "sha512-vA6rkTCabRZu7Nbl9DfLZE1imj4tzdWcg5vtdQGvj+OH9itNNB6hxuRMHuIY8SGnEt1T9g5foqs9LnrHzsqEFg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0.tgz", + "integrity": "sha512-1r1X5DO78WnaAIvs5uC48t41LLckxsYklJrZjNKcevyz83sF2l4RHbw29qrCPr/6ksFsdfRpT/ZgxNWHXRnffg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0.tgz", + "integrity": "sha512-uJBrJhBOEa3D033P95nPHu3nbFwFE9ZgXsfEitzoIXIwqAZWk7uXcg06yFKXz9FSxBH5ucgU/cYdX0IV8ldHKw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.1.3" + } + }, + "@babel/preset-env": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.1.0.tgz", + "integrity": "sha512-ZLVSynfAoDHB/34A17/JCZbyrzbQj59QC1Anyueb4Bwjh373nVPq5/HMph0z+tCmcDjXDe+DlKQq9ywQuvWrQg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.1.0", + "@babel/plugin-proposal-json-strings": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.0.0", + "@babel/plugin-syntax-async-generators": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.1.0", + "@babel/plugin-transform-block-scoped-functions": "^7.0.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.1.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.0.0", + "@babel/plugin-transform-dotall-regex": "^7.0.0", + "@babel/plugin-transform-duplicate-keys": "^7.0.0", + "@babel/plugin-transform-exponentiation-operator": "^7.1.0", + "@babel/plugin-transform-for-of": "^7.0.0", + "@babel/plugin-transform-function-name": "^7.1.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-amd": "^7.1.0", + "@babel/plugin-transform-modules-commonjs": "^7.1.0", + "@babel/plugin-transform-modules-systemjs": "^7.0.0", + "@babel/plugin-transform-modules-umd": "^7.1.0", + "@babel/plugin-transform-new-target": "^7.0.0", + "@babel/plugin-transform-object-super": "^7.1.0", + "@babel/plugin-transform-parameters": "^7.1.0", + "@babel/plugin-transform-regenerator": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-template-literals": "^7.0.0", + "@babel/plugin-transform-typeof-symbol": "^7.0.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "browserslist": "^4.1.0", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.3.0" + }, + "dependencies": { + "browserslist": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.2.0.tgz", + "integrity": "sha512-Berls1CHL7qfQz8Lct6QxYA5d2Tvt4doDWHcjvAISybpd+EKZVppNtXgXhaN6SdrPKo7YLTSZuYBs5cYrSWN8w==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000889", + "electron-to-chromium": "^1.3.73", + "node-releases": "^1.0.0-alpha.12" + } + }, + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "dev": true + } + } + }, + "@babel/template": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", + "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.1.2", + "@babel/types": "^7.1.2" + } + }, + "@babel/traverse": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.0.tgz", + "integrity": "sha512-bwgln0FsMoxm3pLOgrrnGaXk18sSM9JNf1/nHC/FksmNGFbYnPWY4GYCfLxyP1KRmfsxqkRpfoa6xr6VuuSxdw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.0.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "debug": "^3.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.10" + }, + "dependencies": { + "debug": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.1.2.tgz", + "integrity": "sha512-pb1I05sZEKiSlMUV9UReaqsCPUpgbHHHu2n1piRm7JkuBkm6QxcaIzKu6FMnMtCbih/cEYTR+RGYYC96Yk9HAg==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.10", + "to-fast-properties": "^2.0.0" + } + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -43,23 +846,23 @@ }, "dependencies": { "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha1-3Eu8emyp2Rbe5dQ1FvAJK1j3uKs=", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", "dev": true } } }, "acorn": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", - "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", "dev": true }, "acorn-jsx": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", - "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", + "integrity": "sha1-6OQeSOov4MiWdAYQq2pP/YrdIl4=", "dev": true, "requires": { "acorn": "^5.0.3" @@ -81,20 +884,22 @@ "agent-base": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "integrity": "sha1-2J5ZmfeXh1Z0wH2H8mD8Qeg+jKk=", "dev": true, "requires": { "es6-promisify": "^5.0.0" } }, "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", + "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", "dev": true, "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ajv-keywords": { @@ -140,7 +945,7 @@ "amqplib": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.2.tgz", - "integrity": "sha512-l9mCs6LbydtHqRniRwYkKdqxVa6XMz3Vw1fh+2gJaaVgTM6Jk3o8RccAKWKtlhT1US5sWrFh+KKxsVUALURSIA==", + "integrity": "sha1-0tcxPH/6pNELzx5iUt5FkbbMe2M=", "dev": true, "optional": true, "requires": { @@ -153,19 +958,19 @@ }, "angular": { "version": "1.3.20", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.3.20.tgz", + "resolved": "http://registry.npmjs.org/angular/-/angular-1.3.20.tgz", "integrity": "sha1-sjo9fF5/mffZW5tNSbmsNVJpuu4=", "dev": true }, "angular-animate": { "version": "1.3.20", - "resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.3.20.tgz", + "resolved": "http://registry.npmjs.org/angular-animate/-/angular-animate-1.3.20.tgz", "integrity": "sha1-0XB8cn+K0N8hxKLewgzX/ElFtSo=", "dev": true }, "ansi-colors": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { @@ -184,7 +989,7 @@ "ansi-escapes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "integrity": "sha1-9zIHu4EgfXX9bIPxJa8m7qN4yjA=", "dev": true }, "ansi-gray": { @@ -422,7 +1227,7 @@ "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=", "dev": true }, "arrify": { @@ -439,10 +1244,13 @@ "optional": true }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "0.2.0", @@ -459,17 +1267,17 @@ "ast-types": { "version": "0.11.5", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.5.tgz", - "integrity": "sha512-oJjo+5e7/vEc2FBK8gUalV0pba4L3VdBIs2EKhOLHLcOd2FgQIVQN9xb0eZ9IjEWyAL7vq6fGJxOvVvdCHNyMw==", + "integrity": "sha1-mJCCXWYMA8KDOfMV6foKNg4x7Cg=", "dev": true, "optional": true }, "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha1-YaKau2/MAm/qd+VtHG7FOnlZUfQ=", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", "dev": true, "requires": { - "lodash": "^4.14.0" + "lodash": "^4.17.10" } }, "async-each": { @@ -488,7 +1296,7 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=", "dev": true }, "asynckit": { @@ -498,9 +1306,9 @@ "dev": true }, "atob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, "autoprefixer": { @@ -524,14 +1332,14 @@ "dev": true }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha1-1NDpudv8p3vwjusKikcVUP454ok=", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, "axios": { "version": "0.15.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.15.3.tgz", + "resolved": "http://registry.npmjs.org/axios/-/axios-0.15.3.tgz", "integrity": "sha1-LJ1jiy4ZGgjqHWzJiOrda6W9wFM=", "dev": true, "optional": true, @@ -541,7 +1349,7 @@ "dependencies": { "follow-redirects": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/follow-redirects/-/follow-redirects-1.0.0.tgz", "integrity": "sha1-jjQpjL0uF28lTv/sdaHHjMhJ/Tc=", "dev": true, "optional": true, @@ -551,17 +1359,6 @@ } } }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", @@ -649,7 +1446,7 @@ }, "basic-auth": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", + "resolved": "http://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", "integrity": "sha1-Awk1sB3nyblKgksp8/zLdQ06UpA=", "dev": true }, @@ -666,9 +1463,9 @@ "dev": true }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, "optional": true, "requires": { @@ -784,9 +1581,9 @@ } }, "binary-extensions": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", - "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", + "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", "dev": true }, "bitsyntax": { @@ -817,26 +1614,26 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -848,9 +1645,9 @@ "dev": true }, "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha1-2VUfnemPH82h5oPRfukaBgLuLrk=", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", + "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==", "dev": true }, "body-parser": { @@ -873,7 +1670,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -990,6 +1787,12 @@ "integrity": "sha1-vX3CauKXLQ7aJTvgYdupkjScGfA=", "dev": true }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, "buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", @@ -997,11 +1800,10 @@ "dev": true }, "buffer-from": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz", - "integrity": "sha1-FfS5vO8BIETfMRQsFDM8r24CYNA=", - "dev": true, - "optional": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true }, "buffer-more-ints": { "version": "0.0.2", @@ -1035,8 +1837,8 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -1059,7 +1861,7 @@ }, "uuid": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "resolved": "http://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", "dev": true }, @@ -1099,6 +1901,15 @@ "nodemailer-fetch": "1.6.0", "nodemailer-shared": "1.1.0", "punycode": "1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true, + "optional": true + } } }, "builtin-modules": { @@ -1152,9 +1963,9 @@ "dev": true }, "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", "dev": true }, "camelcase-keys": { @@ -1165,14 +1976,6 @@ "requires": { "camelcase": "^2.0.0", "map-obj": "^1.0.0" - }, - "dependencies": { - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - } } }, "caniuse-api": { @@ -1188,9 +1991,15 @@ } }, "caniuse-db": { - "version": "1.0.30000836", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000836.tgz", - "integrity": "sha1-eItsj28CmRdDsYzbvVT5bQW0uVo=", + "version": "1.0.30000889", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000889.tgz", + "integrity": "sha512-Rf9Sbm2KS7s6Rk8iNeI5zJdquqctXBXAfy/bb1tCCYRds5RAaHNdyt2D4z8TSRToDkYsAwiSBV/bFHR+4IgTiw==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30000889", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000889.tgz", + "integrity": "sha512-MFxcQ6x/LEEoaIhO7Zdb7Eg8YyNONN+WBnS5ERJ0li2yRw51+i4xXUNxnLaveTb/4ZoJqsWKEmlomhG2pYzlQA==", "dev": true }, "canonical-path": { @@ -1200,9 +2009,9 @@ "dev": true }, "capture-stack-trace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", - "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", "dev": true }, "caseless": { @@ -1228,12 +2037,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", "dev": true - }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true } } }, @@ -1249,7 +2052,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -1269,9 +2072,9 @@ } }, "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, "chokidar": { @@ -1379,6 +2182,14 @@ "center-align": "^0.1.1", "right-align": "^0.1.1", "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + } } }, "clone": { @@ -1418,8 +2229,8 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -1443,9 +2254,9 @@ } }, "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz", + "integrity": "sha1-TqVOpaCJOBUxheFSEMaNkJK8G3g=", "dev": true }, "coa": { @@ -1479,12 +2290,12 @@ } }, "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha1-wSYRB66y8pTr/+ye2eytUppgl+0=", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { - "color-name": "^1.1.1" + "color-name": "1.1.3" } }, "color-name": { @@ -1520,9 +2331,9 @@ } }, "colors": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.4.tgz", - "integrity": "sha1-4MtB0+SyCAazv8J/RVnwG5S8L3w=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.2.tgz", + "integrity": "sha512-rhP0JSBGYvpcNQj4s5AdShMeE5ahMop96cTeDl/v9qQQm2fYClE2QXZRi8wLzc+GmXSxdIqqbOIAhyObEXDbfQ==", "dev": true }, "combine-lists": { @@ -1535,9 +2346,9 @@ } }, "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "dev": true, "requires": { "delayed-stream": "~1.0.0" @@ -1545,7 +2356,7 @@ }, "commander": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz", "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", "dev": true, "requires": { @@ -1571,12 +2382,12 @@ "dev": true }, "compressible": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.13.tgz", - "integrity": "sha1-DRAgq5JLL9tNYnmHXH1tq6a6p6k=", + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz", + "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==", "dev": true, "requires": { - "mime-db": ">= 1.33.0 < 2" + "mime-db": ">= 1.36.0 < 2" } }, "compression": { @@ -1595,7 +2406,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -1617,11 +2428,12 @@ "dev": true }, "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { + "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^2.2.2", "typedarray": "^0.0.6" @@ -1635,8 +2447,8 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -1717,7 +2529,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -1752,7 +2564,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -1790,10 +2602,13 @@ "dev": true }, "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } }, "cookie": { "version": "0.1.3", @@ -1826,7 +2641,7 @@ "core-js": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "integrity": "sha1-+XJgj/DOrWi4QaFqky0LGDeRgU4=", "dev": true }, "core-util-is": { @@ -1852,7 +2667,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -1874,27 +2689,23 @@ } }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, - "optional": true, "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" }, "dependencies": { - "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha1-oRdc80lt/IQ2wVbDNLSVWZK85pw=", - "dev": true, - "optional": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "dev": true } } }, @@ -1925,15 +2736,15 @@ "dev": true }, "css-select": { - "version": "1.3.0-rc0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.3.0-rc0.tgz", - "integrity": "sha1-b5MZaqrnN2ZuoQNqjLFKj8t6kjE=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.0.tgz", + "integrity": "sha512-MGhoq1S9EyPgZIGnts8Yz5WwUOyHmPMdlqeifsYs/xFX7AAm3hY0RJe1dqVlXtYPI66Nsk39R/sa5/ree6L2qg==", "dev": true, "optional": true, "requires": { "boolbase": "^1.0.0", "css-what": "2.1", - "domutils": "1.5.1", + "domutils": "^1.7.0", "nth-check": "^1.0.1" } }, @@ -1945,13 +2756,13 @@ "optional": true }, "css-tree": { - "version": "1.0.0-alpha25", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha25.tgz", - "integrity": "sha1-G7+r+/bu708B2RCP8u3Qvi/jVZc=", + "version": "1.0.0-alpha.28", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.28.tgz", + "integrity": "sha512-joNNW1gCp3qFFzj4St6zk+Wh/NBv0vM5YbEreZk0SD4S23S+1xBKb6cLDg2uj4P4k/GUMlIm6cKIDqIG+vdt0w==", "dev": true, "optional": true, "requires": { - "mdn-data": "^1.0.0", + "mdn-data": "~1.1.0", "source-map": "^0.5.3" } }, @@ -1971,7 +2782,7 @@ }, "cssnano": { "version": "3.10.0", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", + "resolved": "http://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", "dev": true, "requires": { @@ -2066,7 +2877,7 @@ "data-uri-to-buffer": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", - "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==", + "integrity": "sha1-dxY+qcINhkG0cH6PGKvfmnjzSDU=", "dev": true, "optional": true }, @@ -2201,7 +3012,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -2298,26 +3109,26 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } }, "strip-bom": { @@ -2329,16 +3140,6 @@ "is-utf8": "^0.2.0" } }, - "strip-bom-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", - "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", - "dev": true, - "requires": { - "first-chunk-stream": "^1.0.0", - "strip-bom": "^2.0.0" - } - }, "unique-stream": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.1.tgz", @@ -2415,7 +3216,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -2476,7 +3277,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -2536,7 +3337,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -2618,13 +3419,12 @@ } }, "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "foreach": "^2.0.5", - "object-keys": "^1.0.8" + "object-keys": "^1.0.12" } }, "define-property": { @@ -2708,22 +3508,6 @@ "pify": "^2.0.0", "pinkie-promise": "^2.0.0", "rimraf": "^2.2.8" - }, - "dependencies": { - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - } } }, "delayed-stream": { @@ -2765,7 +3549,7 @@ "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=", "dev": true, "requires": { "esutils": "^2.0.2" @@ -2811,9 +3595,9 @@ "optional": true }, "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", "dev": true, "optional": true, "requires": { @@ -2932,7 +3716,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -3029,26 +3813,26 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } }, "strip-bom": { @@ -3060,16 +3844,6 @@ "is-utf8": "^0.2.0" } }, - "strip-bom-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", - "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", - "dev": true, - "requires": { - "first-chunk-stream": "^1.0.0", - "strip-bom": "^2.0.0" - } - }, "unique-stream": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.1.tgz", @@ -3120,7 +3894,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -3148,10 +3922,10 @@ "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", "dev": true, "requires": { - "once": "1.4.0" + "once": "^1.4.0" } }, "isarray": { @@ -3162,26 +3936,26 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -3194,16 +3968,25 @@ "requires": { "onetime": "^1.0.0", "set-immediate-shim": "^1.0.0" + }, + "dependencies": { + "onetime": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + } } }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "ee-first": { @@ -3213,9 +3996,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.45", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.45.tgz", - "integrity": "sha1-RYrBscXHYM6IEaFtK/vZfsMLr7g=", + "version": "1.3.73", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.73.tgz", + "integrity": "sha512-6PIg7v9zRoVGh6EheRF8h6Plti+3Yo/qtHobS4/Htyt53DNHmKKGFqSae1AIk0k1S4gCQvt7I2WgpbuZNcDY+g==", "dev": true }, "encodeurl": { @@ -3247,7 +4030,7 @@ "engine.io": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.5.tgz", - "integrity": "sha512-D06ivJkYxyRrcEe0bTpNnBQNgP9d3xog+qZlLbui8EsMr/DouQpf5o9FzJnWYHEYE0YsFHllUv2R1dkgYZXHcA==", + "integrity": "sha1-Dn751pDrCzVZfx1K0Comyi26OEU=", "dev": true, "requires": { "accepts": "~1.3.4", @@ -3278,7 +4061,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -3295,7 +4078,7 @@ "engine.io-client": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.6.tgz", - "integrity": "sha512-hnuHsFluXnsKOndS4Hv6SvUrgdYx1pk2NqfaDMW+GWdgfU3+/V25Cj7I8a0x92idSpa5PIhJRKxPvp9mnoLsfg==", + "integrity": "sha1-W96xMPi5SlCsXL63JYPnpKBj3f0=", "dev": true, "requires": { "component-emitter": "1.2.1", @@ -3314,7 +4097,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -3325,7 +4108,7 @@ "engine.io-parser": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.2.tgz", - "integrity": "sha512-dInLFzr80RijZ1rGpx1+56/uFoH7/7InhH3kZt+Ms6hT8tNx3NGW/WNSA/f8As1WkOfkuyb3tnRyuXGxusclMw==", + "integrity": "sha1-TA9M/3mq7su9z96maoI8YIVAkZY=", "dev": true, "requires": { "after": "0.8.2", @@ -3359,9 +4142,9 @@ } }, "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { "is-arrayish": "^0.2.1" @@ -3409,19 +4192,19 @@ } }, "es-to-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", "dev": true, "requires": { - "is-callable": "^1.1.1", + "is-callable": "^1.1.4", "is-date-object": "^1.0.1", - "is-symbol": "^1.0.1" + "is-symbol": "^1.0.2" } }, "es6-promise": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "resolved": "http://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", "dev": true }, @@ -3435,9 +4218,9 @@ }, "dependencies": { "es6-promise": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", - "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", + "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", "dev": true } } @@ -3455,9 +4238,9 @@ "dev": true }, "escodegen": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", - "integrity": "sha1-264X75bI5L7bE1b0UE+kzC98t+I=", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", + "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", "dev": true, "requires": { "esprima": "^3.1.3", @@ -3483,16 +4266,16 @@ } }, "eslint": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.3.0.tgz", - "integrity": "sha512-N/tCqlMKkyNvAvLu+zI9AqDasnSLt00K+Hu8kdsERliC9jYEc8ck12XtjvOXrBKu8fK6RrBcN9bat6Xk++9jAg==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.6.1.tgz", + "integrity": "sha512-hgrDtGWz368b7Wqf+v1Z69O3ZebNR0+GA7PtDdbmuz4rInFVUV9uw7whjZEiWyLzCjVb5Rs5WRN1TAS6eo7AYA==", "dev": true, "requires": { - "ajv": "^6.5.0", - "babel-code-frame": "^6.26.0", + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", "chalk": "^2.1.0", "cross-spawn": "^6.0.5", - "debug": "^3.1.0", + "debug": "^4.0.1", "doctrine": "^2.1.0", "eslint-scope": "^4.0.0", "eslint-utils": "^1.3.1", @@ -3504,11 +4287,11 @@ "functional-red-black-tree": "^1.0.1", "glob": "^7.1.2", "globals": "^11.7.0", - "ignore": "^4.0.2", + "ignore": "^4.0.6", "imurmurhash": "^0.1.4", - "inquirer": "^5.2.0", + "inquirer": "^6.1.0", "is-resolvable": "^1.1.0", - "js-yaml": "^3.11.0", + "js-yaml": "^3.12.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", "lodash": "^4.17.5", @@ -3521,26 +4304,13 @@ "progress": "^2.0.0", "regexpp": "^2.0.0", "require-uncached": "^1.0.3", - "semver": "^5.5.0", - "string.prototype.matchall": "^2.0.0", + "semver": "^5.5.1", "strip-ansi": "^4.0.0", "strip-json-comments": "^2.0.1", "table": "^4.0.3", "text-table": "^0.2.0" }, "dependencies": { - "ajv": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", - "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.1" - } - }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", @@ -3567,26 +4337,13 @@ "supports-color": "^5.3.0" } }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.0.1.tgz", + "integrity": "sha512-K23FHJ/Mt404FSlp6gSZCevIbTMLX0j3fmHhUEhQ3Wq0FMODW3+cUSoLdy1Gx4polAf4t/lphhmHH35BB8cLYw==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "esprima": { @@ -3611,16 +4368,16 @@ "esprima": "^4.0.0" } }, - "progress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", "dev": true }, "strip-ansi": { @@ -3633,9 +4390,9 @@ } }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -3646,7 +4403,7 @@ "eslint-scope": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "integrity": "sha1-UL8wcekzi83EMzF5Sgy1M/ATYXI=", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -3656,19 +4413,19 @@ "eslint-utils": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "integrity": "sha1-moUbqJ7nxGA0b5fPiTnHKYgn5RI=", "dev": true }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=", "dev": true }, "espree": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/espree/-/espree-4.0.0.tgz", - "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", + "integrity": "sha1-JTmY8goPgttdhmOFeZ2RKoOjZjQ=", "dev": true, "requires": { "acorn": "^5.6.0", @@ -3684,7 +4441,7 @@ "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -3693,7 +4450,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -3728,24 +4485,25 @@ "dev": true }, "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz", + "integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==", "dev": true, "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" + "duplexer": "^0.1.1", + "flatmap-stream": "^0.1.0", + "from": "^0.1.7", + "map-stream": "0.0.7", + "pause-stream": "^0.0.11", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through": "^2.3.8" } }, "eventemitter3": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", - "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", + "integrity": "sha1-CQtNbNvWRe0Qv3UNS1QHlC17oWM=", "dev": true }, "exec-buffer": { @@ -3796,6 +4554,31 @@ "p-finally": "^1.0.0", "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "optional": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "dev": true, + "optional": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + } } }, "executable": { @@ -3984,7 +4767,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -4009,9 +4792,9 @@ } }, "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, "extend-shallow": { @@ -4036,33 +4819,24 @@ } }, "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", "tmp": "^0.0.33" }, "dependencies": { "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } } } }, @@ -4132,24 +4906,33 @@ } }, "extract-zip": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.6.tgz", - "integrity": "sha1-EpDt6NINCHK0Kf0/NRyhKOxe+Fw=", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", "dev": true, "requires": { - "concat-stream": "1.6.0", + "concat-stream": "1.6.2", "debug": "2.6.9", - "mkdirp": "0.5.0", + "mkdirp": "0.5.1", "yauzl": "2.4.1" }, "dependencies": { - "mkdirp": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", - "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", "dev": true, "requires": { - "minimist": "0.0.8" + "pend": "~1.2.0" + } + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "requires": { + "fd-slicer": "~1.0.1" } } } @@ -4199,23 +4982,21 @@ } }, "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, "requires": { "pend": "~1.2.0" } }, "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, - "optional": true, "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" + "escape-string-regexp": "^1.0.5" } }, "file-entry-cache": { @@ -4237,7 +5018,7 @@ "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "integrity": "sha1-VTp7hEb/b2hDWcRF8eN6BdrMM90=", "dev": true, "optional": true }, @@ -4289,7 +5070,7 @@ }, "finalhandler": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", "integrity": "sha1-llpS2ejQXSuFdUhUH7ibU6JJfZs=", "dev": true, "requires": { @@ -4301,7 +5082,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -4408,6 +5189,17 @@ } } }, + "flatmap-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/flatmap-stream/-/flatmap-stream-0.1.0.tgz", + "integrity": "sha512-Nlic4ZRYxikqnK5rj3YoxDVKGGtUjcNDUtvQ7XsdGLZmMwdUYnXf10o1zcXtzEZTBgc6GxeRpQxV/Wu3WPIIHA==", + "dev": true + }, + "flatpickr": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.5.2.tgz", + "integrity": "sha512-jDy4QYGpmiy7+Qk8QvKJ4spjDdxcx9cxMydmq1x427HkKWBw0qizLYeYM2F6tMcvvqGjU5VpJS55j4LnsaBblA==" + }, "flatten": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", @@ -4415,12 +5207,12 @@ "dev": true }, "follow-redirects": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.2.tgz", - "integrity": "sha512-kssLorP/9acIdpQ2udQVTiCS5LQmdEz9mvdIfDcl1gYX2tPKFADHSyFdvJS040XdFsPzemWtgI3q8mFVCxtX8A==", + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.8.tgz", + "integrity": "sha512-sy1mXPmv7kLAMKW/8XofG7o9T+6gAjzdZK4AJF6ryqQYUa/hnzgiypoeUecZ53x7XiqKNEpNqLtS97MshW2nxg==", "dev": true, "requires": { - "debug": "^3.1.0" + "debug": "=3.1.0" }, "dependencies": { "debug": { @@ -4449,12 +5241,6 @@ "for-in": "^1.0.1" } }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -4575,21 +5361,23 @@ "dev": true, "optional": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "delegates": "1.0.0", + "readable-stream": "2.3.6" } }, "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -4602,17 +5390,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4653,7 +5444,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.2.4" } }, "fs.realpath": { @@ -4668,14 +5459,14 @@ "dev": true, "optional": true, "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" } }, "glob": { @@ -4684,12 +5475,12 @@ "dev": true, "optional": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "has-unicode": { @@ -4704,7 +5495,7 @@ "dev": true, "optional": true, "requires": { - "safer-buffer": "^2.1.0" + "safer-buffer": "2.1.2" } }, "ignore-walk": { @@ -4713,7 +5504,7 @@ "dev": true, "optional": true, "requires": { - "minimatch": "^3.0.4" + "minimatch": "3.0.4" } }, "inflight": { @@ -4722,14 +5513,15 @@ "dev": true, "optional": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -4741,8 +5533,9 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "isarray": { @@ -4755,22 +5548,25 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.11" } }, "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { - "safe-buffer": "^5.1.1", - "yallist": "^3.0.0" + "safe-buffer": "5.1.1", + "yallist": "3.0.2" } }, "minizlib": { @@ -4779,13 +5575,14 @@ "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.2.4" } }, "mkdirp": { "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -4802,9 +5599,9 @@ "dev": true, "optional": true, "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" + "debug": "2.6.9", + "iconv-lite": "0.4.21", + "sax": "1.2.4" } }, "node-pre-gyp": { @@ -4813,16 +5610,16 @@ "dev": true, "optional": true, "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.0", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.1.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" + "detect-libc": "1.0.3", + "mkdirp": "0.5.1", + "needle": "2.2.0", + "nopt": "4.0.1", + "npm-packlist": "1.1.10", + "npmlog": "4.1.2", + "rc": "1.2.7", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "4.4.1" } }, "nopt": { @@ -4831,8 +5628,8 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1.1.1", + "osenv": "0.1.5" } }, "npm-bundled": { @@ -4847,8 +5644,8 @@ "dev": true, "optional": true, "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" } }, "npmlog": { @@ -4857,16 +5654,17 @@ "dev": true, "optional": true, "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" } }, "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4878,8 +5676,9 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "os-homedir": { @@ -4900,8 +5699,8 @@ "dev": true, "optional": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, "path-is-absolute": { @@ -4922,10 +5721,10 @@ "dev": true, "optional": true, "requires": { - "deep-extend": "^0.5.1", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "deep-extend": "0.5.1", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" }, "dependencies": { "minimist": { @@ -4942,13 +5741,13 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "rimraf": { @@ -4957,7 +5756,7 @@ "dev": true, "optional": true, "requires": { - "glob": "^7.0.5" + "glob": "7.1.2" } }, "safe-buffer": { @@ -4999,10 +5798,11 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "string_decoder": { @@ -5011,7 +5811,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "strip-ansi": { @@ -5019,7 +5819,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-json-comments": { @@ -5034,13 +5834,13 @@ "dev": true, "optional": true, "requires": { - "chownr": "^1.0.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.2.4", - "minizlib": "^1.1.0", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.1", - "yallist": "^3.0.2" + "chownr": "1.0.1", + "fs-minipass": "1.2.5", + "minipass": "2.2.4", + "minizlib": "1.1.0", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.1", + "yallist": "3.0.2" } }, "util-deprecate": { @@ -5055,7 +5855,7 @@ "dev": true, "optional": true, "requires": { - "string-width": "^1.0.2" + "string-width": "1.0.2" } }, "wrappy": { @@ -5103,11 +5903,14 @@ } }, "generate-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", "dev": true, - "optional": true + "optional": true, + "requires": { + "is-property": "^1.0.2" + } }, "generate-object-property": { "version": "1.2.0", @@ -5136,7 +5939,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true, "optional": true @@ -5144,7 +5947,7 @@ "get-uri": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.2.tgz", - "integrity": "sha512-ZD325dMZOgerGqF/rF6vZXyFGTAay62svjQIT+X/oU2PtxYpFxvSkbsdi+oxIrsNxlZVd4y8wUDqkaExWTI/Cw==", + "integrity": "sha1-XHlecTJvbKEoby/IJXXNK6sq9Xg=", "dev": true, "optional": true, "requires": { @@ -5165,7 +5968,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, @@ -5182,7 +5985,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "optional": true, "requires": { @@ -5227,9 +6030,9 @@ } }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -5323,7 +6126,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -5388,18 +6191,19 @@ } }, "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz", + "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==", "dev": true }, "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", "dev": true, "requires": { "array-union": "^1.0.1", + "arrify": "^1.0.0", "glob": "^7.0.3", "object-assign": "^4.0.1", "pify": "^2.0.0", @@ -5442,7 +6246,7 @@ }, "lodash": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", "dev": true }, @@ -5469,7 +6273,7 @@ }, "got": { "version": "5.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz", + "resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz", "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=", "dev": true, "requires": { @@ -5507,17 +6311,17 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -5526,7 +6330,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -5569,12 +6373,32 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } } }, + "gulp-babel": { + "version": "8.0.0-beta.2", + "resolved": "http://registry.npmjs.org/gulp-babel/-/gulp-babel-8.0.0-beta.2.tgz", + "integrity": "sha512-GTC2PxAXWkp6u1fP+C5+kn5biQ0dKGhkOSSXvKAf3ykF0+R3tevmLm/zSIkc1+S7U1JwH3XTvuMwRL6LD+sEiw==", + "dev": true, + "requires": { + "plugin-error": "^1.0.1", + "replace-ext": "^1.0.0", + "through2": "^2.0.0", + "vinyl-sourcemaps-apply": "^0.2.0" + }, + "dependencies": { + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + } + } + }, "gulp-concat": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", @@ -5587,9 +6411,9 @@ }, "dependencies": { "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", "dev": true }, "clone-stats": { @@ -5605,9 +6429,9 @@ "dev": true }, "vinyl": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", - "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", "dev": true, "requires": { "clone": "^2.1.1", @@ -5622,7 +6446,7 @@ }, "gulp-connect": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gulp-connect/-/gulp-connect-5.0.0.tgz", + "resolved": "http://registry.npmjs.org/gulp-connect/-/gulp-connect-5.0.0.tgz", "integrity": "sha1-8v3zBq6RFGg2jCKF8teC8T7dr04=", "dev": true, "requires": { @@ -5653,26 +6477,26 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -5680,26 +6504,12 @@ "gulp-eslint": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-5.0.0.tgz", - "integrity": "sha512-9GUqCqh85C7rP9120cpxXuZz2ayq3BZc85pCTuPJS03VQYxne0aWPIXWx6LSvsGPa3uRqtSO537vaugOh+5cXg==", + "integrity": "sha1-KiaECV93Syz3kxAmIHjFbMehK1I=", "dev": true, "requires": { "eslint": "^5.0.1", "fancy-log": "^1.3.2", "plugin-error": "^1.0.1" - }, - "dependencies": { - "plugin-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", - "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", - "dev": true, - "requires": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" - } - } } }, "gulp-imagemin": { @@ -5730,6 +6540,28 @@ "color-convert": "^1.9.0" } }, + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, "chalk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", @@ -5741,16 +6573,44 @@ "supports-color": "^5.3.0" } }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "^1.1.0" + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "kind-of": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + } + }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha1-HGszdALCE3YF7+GfEP7DkPb6q1Q=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -5773,6 +6633,56 @@ "vinyl-sourcemaps-apply": "^0.2.0" }, "dependencies": { + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "^1.1.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + } + }, "replace-ext": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", @@ -5803,7 +6713,7 @@ "dependencies": { "ansi-regex": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "resolved": "http://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", "dev": true }, @@ -5815,7 +6725,7 @@ }, "chalk": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", "dev": true, "requires": { @@ -5888,7 +6798,7 @@ }, "lodash": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz", + "resolved": "http://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz", "integrity": "sha1-W3cjA03aTSYuWkb7LFjXzCL3FCA=", "dev": true }, @@ -5978,13 +6888,13 @@ }, "minimist": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=", "dev": true }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -5996,7 +6906,7 @@ }, "strip-ansi": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", "dev": true, "requires": { @@ -6097,9 +7007,9 @@ } }, "gulp-rename": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.2.2.tgz", - "integrity": "sha1-OtRCh2PwXidk3sHGfYaNsnVoeBc=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", + "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==", "dev": true }, "gulp-sort": { @@ -6180,7 +7090,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -6218,8 +7128,8 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -6328,15 +7238,33 @@ "requires": { "ajv": "^4.9.1", "har-schema": "^1.0.5" + }, + "dependencies": { + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + } } }, "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "^1.0.2" + "function-bind": "^1.1.1" } }, "has-ansi": { @@ -6351,7 +7279,7 @@ "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", "dev": true, "requires": { "isarray": "2.0.1" @@ -6473,9 +7401,9 @@ } }, "hosted-git-info": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha1-IyNbKasjDFdqqw1PE/wEawsDgiI=", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, "html-comment-regex": { @@ -6486,7 +7414,7 @@ }, "http-errors": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", "dev": true, "requires": { @@ -6495,15 +7423,15 @@ } }, "http-parser-js": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.12.tgz", - "integrity": "sha1-uc+/Sizybw/DSxDKFImid3HjR08=", + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz", + "integrity": "sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc=", "dev": true }, "http-proxy": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "integrity": "sha1-etOElGWPhGBeL220Q230EPTlvpo=", "dev": true, "requires": { "eventemitter3": "^3.0.0", @@ -6514,7 +7442,7 @@ "http-proxy-agent": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "integrity": "sha1-5IIb7vWyFCogJr1zkm/lN2McVAU=", "dev": true, "requires": { "agent-base": "4", @@ -6524,7 +7452,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -6562,7 +7490,7 @@ "https-proxy-agent": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", - "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "integrity": "sha1-UVUpcPoE1yPgTFbQQXjD+SWSu8A=", "dev": true, "requires": { "agent-base": "^4.1.0", @@ -6570,26 +7498,32 @@ }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, "iconv-lite": { "version": "0.4.11", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", + "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", "integrity": "sha1-LstC/SlHRJIiCaLnxATayHk9it4=", "dev": true }, "ignore": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.3.tgz", - "integrity": "sha512-Z/vAH2GGIEATQnBVXMclE2IGV6i0GyVngKThcGZ5kHgHMxLo9Ow2+XHRq1aEKEej5vOF1TPJNbvX6J/anT0M7A==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, "image-size": { @@ -6613,6 +7547,19 @@ "replace-ext": "^1.0.0" }, "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, "replace-ext": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", @@ -6669,6 +7616,13 @@ "svgo": "^1.0.0" }, "dependencies": { + "buffer-from": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz", + "integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==", + "dev": true, + "optional": true + }, "coa": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.1.tgz", @@ -6717,9 +7671,9 @@ "optional": true }, "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha1-LnhEFka9RoLpY/IrbpKCPDCcYtw=", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", "dev": true, "optional": true, "requires": { @@ -6728,20 +7682,20 @@ } }, "svgo": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.0.5.tgz", - "integrity": "sha1-cEA2TAYqBTirrP9EAc6momp6OJo=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.1.1.tgz", + "integrity": "sha512-GBkJbnTuFpM4jFbiERHDWhZc/S/kpHToqmZag3aEBjPYK44JAN2QBjvrGIxLOoCyMZjuFQIfTO2eJd8uwLY/9g==", "dev": true, "optional": true, "requires": { "coa": "~2.0.1", "colors": "~1.1.2", - "css-select": "~1.3.0-rc0", + "css-select": "^2.0.0", "css-select-base-adapter": "~0.1.0", - "css-tree": "1.0.0-alpha25", + "css-tree": "1.0.0-alpha.28", "css-url-regex": "^1.1.0", "csso": "^3.5.0", - "js-yaml": "~3.10.0", + "js-yaml": "^3.12.0", "mkdirp": "~0.5.1", "object.values": "^1.0.4", "sax": "~1.2.4", @@ -6815,21 +7769,21 @@ "dev": true }, "inquirer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", - "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", + "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", "dev": true, "requires": { "ansi-escapes": "^3.0.0", "chalk": "^2.0.0", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^2.1.0", + "external-editor": "^3.0.0", "figures": "^2.0.0", - "lodash": "^4.3.0", + "lodash": "^4.17.10", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rxjs": "^5.5.2", + "rxjs": "^6.1.0", "string-width": "^2.1.0", "strip-ansi": "^4.0.0", "through": "^2.3.6" @@ -6861,15 +7815,6 @@ "supports-color": "^5.3.0" } }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -6886,9 +7831,9 @@ } }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -6902,6 +7847,15 @@ "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", "dev": true }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -6986,7 +7940,7 @@ }, "is-builtin-module": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { @@ -7130,14 +8084,14 @@ "is-my-ip-valid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "integrity": "sha1-ezUbjo7dTTmV1NBmaA5mTZRpaCQ=", "dev": true, "optional": true }, "is-my-json-valid": { - "version": "2.17.2", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", - "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz", + "integrity": "sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q==", "dev": true, "optional": true, "requires": { @@ -7176,27 +8130,10 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, - "is-odd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", - "integrity": "sha1-dkZiRnH9fqVYzNmieVGC8pWPGyQ=", - "dev": true, - "requires": { - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha1-ACbjf1RU1z41bf5lZGmYZ8an8P8=", - "dev": true - } - } - }, "is-path-cwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", @@ -7206,7 +8143,7 @@ "is-path-in-cwd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "integrity": "sha1-WsSLNF72dTOb1sekipEhELJBz1I=", "dev": true, "requires": { "is-path-inside": "^1.0.0" @@ -7265,8 +8202,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true, - "optional": true + "dev": true }, "is-redirect": { "version": "1.0.0", @@ -7295,7 +8231,7 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", "dev": true }, "is-retry-allowed": { @@ -7320,10 +8256,13 @@ } }, "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } }, "is-tar": { "version": "1.0.0", @@ -7428,15 +8367,21 @@ } }, "js-base64": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz", - "integrity": "sha1-LlRewrDylX9BNWUQIFIU6Y+tZYI=", + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.9.tgz", + "integrity": "sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ==", + "dev": true + }, + "js-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.4.tgz", + "integrity": "sha512-PxfGzSs0ztShKrUYPIn5r0MtyAhYcCwmndozzpz8YObbPnD1jFxzlBGbRnX2mIu6Z13xN6+PTu05TQFnZFlzow==", "dev": true }, "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "js-yaml": { @@ -7456,6 +8401,12 @@ "dev": true, "optional": true }, + "jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -7465,7 +8416,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", "dev": true }, "json-stable-stringify": { @@ -7489,9 +8440,15 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, + "json5": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, "jsonfile": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { @@ -7543,7 +8500,7 @@ "karma": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/karma/-/karma-2.0.5.tgz", - "integrity": "sha512-rECezBeY7mjzGUWhFlB7CvPHgkHJLXyUmWg+6vHCEsdWNUTnmiS6jRrIMcJEWgU2DUGZzGWG0bTRVky8fsDTOA==", + "integrity": "sha1-NxDHoucbHEOTE/KDhG2I4E5PkYw=", "dev": true, "requires": { "bluebird": "^3.3.0", @@ -7578,7 +8535,7 @@ "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "integrity": "sha1-vLJLTzeTTZqnrBe0ra+J58du8us=", "dev": true, "requires": { "micromatch": "^3.1.4", @@ -7612,7 +8569,7 @@ "chokidar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", - "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "integrity": "sha1-NW/04rDo5D4yLRijckYLvPOszSY=", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -7679,7 +8636,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -7692,7 +8649,7 @@ "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "integrity": "sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM=", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -7710,7 +8667,7 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=", "dev": true }, "range-parser": { @@ -7722,7 +8679,7 @@ "raw-body": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "integrity": "sha1-GzJOzmtXBuFThVvBFIxlu39uoMM=", "dev": true, "requires": { "bytes": "3.0.0", @@ -7734,7 +8691,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true }, "utils-merge": { @@ -7821,26 +8778,26 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -7899,7 +8856,7 @@ "dependencies": { "iconv-lite": { "version": "0.4.15", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", + "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=", "dev": true } @@ -7935,7 +8892,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -7964,9 +8921,9 @@ } }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha1-G3eTz3JZ6jj7NmHU04syYK+K5Oc=", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, "lodash._basecopy": { @@ -8247,7 +9204,7 @@ "log4js": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/log4js/-/log4js-2.11.0.tgz", - "integrity": "sha512-z1XdwyGFg8/WGkOyF6DPJjivCWNLKrklGdViywdYnSKOvgtEBo2UyEMZS5sD2mZrQlU3TvO8wDWLc8mzE1ncBQ==", + "integrity": "sha1-vzkC7/ZcaSPZzpz70ttUFg40AFo=", "dev": true, "requires": { "amqplib": "^0.5.2", @@ -8266,24 +9223,30 @@ }, "dependencies": { "circular-json": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.5.tgz", - "integrity": "sha512-13YaR6kiz0kBNmIVM87Io8Hp7bWOo4r61vkEANy8iH9R9bc6avud/1FT0SBpqR1RpIQADOh/Q+yHZDA1iL6ysA==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.7.tgz", + "integrity": "sha512-/pXoV1JA847qRKPrHbBK6YIBGFF8GOP4wzSgUOA7q0ew0vAv0iJswP+2/nZQ9uzA3Azi7eTrg9L2yzXc/7ZMIA==", "dev": true }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", "dev": true } } @@ -8297,6 +9260,19 @@ "requires": { "figures": "^1.3.5", "squeak": "^1.0.0" + }, + "dependencies": { + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "optional": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + } } }, "loggly": { @@ -8329,9 +9305,9 @@ "optional": true }, "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz", + "integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==", "dev": true, "optional": true }, @@ -8390,7 +9366,7 @@ }, "readable-stream": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "dev": true, "optional": true, @@ -8405,7 +9381,7 @@ }, "request": { "version": "2.75.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.75.0.tgz", + "resolved": "http://registry.npmjs.org/request/-/request-2.75.0.tgz", "integrity": "sha1-0rgmiihtoT6qXQGt9dGMyQ9lfZM=", "dev": true, "optional": true, @@ -8432,13 +9408,6 @@ "tough-cookie": "~2.3.0", "tunnel-agent": "~0.4.1" } - }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true, - "optional": true } } }, @@ -8448,6 +9417,15 @@ "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", "dev": true }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -8483,12 +9461,6 @@ "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", "dev": true }, - "macaddress": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/macaddress/-/macaddress-0.2.8.tgz", - "integrity": "sha1-WQTcU3w57G2+/q6QIycTX6hRHxI=", - "dev": true - }, "mailcomposer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-4.0.1.tgz", @@ -8503,7 +9475,7 @@ "mailgun-js": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/mailgun-js/-/mailgun-js-0.18.1.tgz", - "integrity": "sha512-lvuMP14u24HS2uBsJEnzSyPMxzU2b99tQsIx1o6QNjqxjk8b3WvR+vq5oG1mjqz/IBYo+5gF+uSoDS0RkMVHmg==", + "integrity": "sha1-7jmqGNe7WYpc6e3oSvtoHe/IprA=", "dev": true, "optional": true, "requires": { @@ -8518,10 +9490,20 @@ "tsscmp": "~1.0.0" }, "dependencies": { + "combined-stream": { + "version": "1.0.6", + "resolved": "http://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "optional": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "optional": true, "requires": { @@ -8581,9 +9563,9 @@ "dev": true }, "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", "dev": true }, "map-visit": { @@ -8597,7 +9579,7 @@ }, "marked": { "version": "0.3.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.2.tgz", + "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.2.tgz", "integrity": "sha1-AV2xWIZEOPJKZL3WGgQotBhwbQk=", "dev": true }, @@ -8645,7 +9627,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -8668,8 +9650,8 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -8746,24 +9728,24 @@ "dev": true }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha1-o0kgUKXLm2NFBUHjnZeI0icng9s=", + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==", "dev": true }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha1-bzI/YKg9ERRvgx/xH9ZuL+VQO7g=", + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", "dev": true, "requires": { - "mime-db": "~1.33.0" + "mime-db": "~1.36.0" } }, "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", "dev": true }, "minimatch": { @@ -8777,7 +9759,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, @@ -8804,7 +9786,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -8813,7 +9795,7 @@ }, "morgan": { "version": "1.6.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", + "resolved": "http://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", "integrity": "sha1-X9gYOYxoGcuiinzWZk8pL+HAu/I=", "dev": true, "requires": { @@ -8826,7 +9808,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -8873,16 +9855,16 @@ "dev": true }, "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", "dev": true, "optional": true }, "nanomatch": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", - "integrity": "sha1-h59xUMstq3pHElkGbBBO7m4Pp8I=", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -8890,7 +9872,6 @@ "define-property": "^2.0.2", "extend-shallow": "^3.0.2", "fragment-cache": "^0.2.1", - "is-odd": "^2.0.0", "is-windows": "^1.0.2", "kind-of": "^6.0.2", "object.pick": "^1.3.0", @@ -8900,9 +9881,9 @@ } }, "natives": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.3.tgz", - "integrity": "sha1-RKV5vmRQfqLW7RygSpQVkVz3VVg=", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.5.tgz", + "integrity": "sha512-1pJ+02gl2KJgCPFtpZGtuD4lGSJnIZvvFHCQTOeDRMSXjfu2GmYWuhI8NFMA4W2I5NNFRbfy/YCiVt4CgNpP8A==", "dev": true }, "natural-compare": { @@ -8925,9 +9906,9 @@ "optional": true }, "nice-try": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", - "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, "node-fs": { @@ -8936,6 +9917,23 @@ "integrity": "sha1-MjI8zLRsn78PwRgS1FAhzDHTJbs=", "dev": true }, + "node-releases": { + "version": "1.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.0.0-alpha.12.tgz", + "integrity": "sha512-VPB4rTPqpVyWKBHbSa4YPFme3+8WHsOSpvbp0Mfj0bWsC8TEjt4HQrLl1hsBDELlp1nB4lflSgSuGTYiuyaP7Q==", + "dev": true, + "requires": { + "semver": "^5.3.0" + }, + "dependencies": { + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "dev": true + } + } + }, "node-status-codes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", @@ -8967,6 +9965,13 @@ "socks": "1.1.9" }, "dependencies": { + "smart-buffer": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", + "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=", + "dev": true, + "optional": true + }, "socks": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.9.tgz", @@ -9084,6 +10089,2702 @@ "sort-keys": "^1.0.0" } }, + "npm": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.4.1.tgz", + "integrity": "sha512-mXJL1NTVU136PtuopXCUQaNWuHlXCTp4McwlSW8S9/Aj8OEPAlSBgo8og7kJ01MjCDrkmqFQTvN5tTEhBMhXQg==", + "requires": { + "JSONStream": "^1.3.4", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "aproba": "~1.2.0", + "archy": "~1.0.0", + "bin-links": "^1.1.2", + "bluebird": "~3.5.1", + "byte-size": "^4.0.3", + "cacache": "^11.2.0", + "call-limit": "~1.1.0", + "chownr": "~1.0.1", + "ci-info": "^1.4.0", + "cli-columns": "^3.1.2", + "cli-table3": "^0.5.0", + "cmd-shim": "~2.0.2", + "columnify": "~1.5.4", + "config-chain": "~1.1.11", + "debuglog": "*", + "detect-indent": "~5.0.0", + "detect-newline": "^2.1.0", + "dezalgo": "~1.0.3", + "editor": "~1.0.0", + "figgy-pudding": "^3.4.1", + "find-npm-prefix": "^1.0.2", + "fs-vacuum": "~1.2.10", + "fs-write-stream-atomic": "~1.0.10", + "gentle-fs": "^2.0.1", + "glob": "~7.1.2", + "graceful-fs": "~4.1.11", + "has-unicode": "~2.0.1", + "hosted-git-info": "^2.7.1", + "iferr": "^1.0.2", + "imurmurhash": "*", + "inflight": "~1.0.6", + "inherits": "~2.0.3", + "ini": "^1.3.5", + "init-package-json": "^1.10.3", + "is-cidr": "^2.0.6", + "json-parse-better-errors": "^1.0.2", + "lazy-property": "~1.0.0", + "libcipm": "^2.0.2", + "libnpmhook": "^4.0.1", + "libnpx": "^10.2.0", + "lock-verify": "^2.0.2", + "lockfile": "^1.0.4", + "lodash._baseindexof": "*", + "lodash._baseuniq": "~4.6.0", + "lodash._bindcallback": "*", + "lodash._cacheindexof": "*", + "lodash._createcache": "*", + "lodash._getnative": "*", + "lodash.clonedeep": "~4.5.0", + "lodash.restparam": "*", + "lodash.union": "~4.6.0", + "lodash.uniq": "~4.5.0", + "lodash.without": "~4.4.0", + "lru-cache": "^4.1.3", + "meant": "~1.0.1", + "mississippi": "^3.0.0", + "mkdirp": "~0.5.1", + "move-concurrently": "^1.0.1", + "node-gyp": "^3.8.0", + "nopt": "~4.0.1", + "normalize-package-data": "~2.4.0", + "npm-audit-report": "^1.3.1", + "npm-cache-filename": "~1.0.2", + "npm-install-checks": "~3.0.0", + "npm-lifecycle": "^2.1.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.11", + "npm-pick-manifest": "^2.1.0", + "npm-profile": "^3.0.2", + "npm-registry-client": "^8.6.0", + "npm-registry-fetch": "^1.1.0", + "npm-user-validate": "~1.0.0", + "npmlog": "~4.1.2", + "once": "~1.4.0", + "opener": "^1.5.0", + "osenv": "^0.1.5", + "pacote": "^8.1.6", + "path-is-inside": "~1.0.2", + "promise-inflight": "~1.0.1", + "qrcode-terminal": "^0.12.0", + "query-string": "^6.1.0", + "qw": "~1.0.1", + "read": "~1.0.7", + "read-cmd-shim": "~1.0.1", + "read-installed": "~4.0.3", + "read-package-json": "^2.0.13", + "read-package-tree": "^5.2.1", + "readable-stream": "^2.3.6", + "readdir-scoped-modules": "*", + "request": "^2.88.0", + "retry": "^0.12.0", + "rimraf": "~2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.5.0", + "sha": "~2.0.1", + "slide": "~1.1.6", + "sorted-object": "~2.0.1", + "sorted-union-stream": "~2.1.3", + "ssri": "^6.0.0", + "stringify-package": "^1.0.0", + "tar": "^4.4.6", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "uid-number": "0.0.6", + "umask": "~1.1.0", + "unique-filename": "~1.1.0", + "unpipe": "~1.0.0", + "update-notifier": "^2.5.0", + "uuid": "^3.3.2", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^1.3.1", + "worker-farm": "^1.6.0", + "write-file-atomic": "^2.3.0" + }, + "dependencies": { + "JSONStream": { + "version": "1.3.4", + "bundled": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "3.4.1", + "bundled": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "ajv": { + "version": "5.5.2", + "bundled": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ansi-align": { + "version": "2.0.0", + "bundled": true, + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true + }, + "ansistyles": { + "version": "0.1.3", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "archy": { + "version": "1.0.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "asap": { + "version": "2.0.6", + "bundled": true + }, + "asn1": { + "version": "0.2.4", + "bundled": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "bundled": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true + }, + "aws-sign2": { + "version": "0.7.0", + "bundled": true + }, + "aws4": { + "version": "1.8.0", + "bundled": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bin-links": { + "version": "1.1.2", + "bundled": true, + "requires": { + "bluebird": "^3.5.0", + "cmd-shim": "^2.0.2", + "gentle-fs": "^2.0.0", + "graceful-fs": "^4.1.11", + "write-file-atomic": "^2.3.0" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "bluebird": { + "version": "3.5.1", + "bundled": true + }, + "boxen": { + "version": "1.3.0", + "bundled": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.0.0", + "bundled": true + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true + }, + "builtins": { + "version": "1.0.3", + "bundled": true + }, + "byline": { + "version": "5.0.0", + "bundled": true + }, + "byte-size": { + "version": "4.0.3", + "bundled": true + }, + "cacache": { + "version": "11.2.0", + "bundled": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "figgy-pudding": "^3.1.0", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.3", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^6.0.0", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "call-limit": { + "version": "1.1.0", + "bundled": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true + }, + "capture-stack-trace": { + "version": "1.0.0", + "bundled": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true + }, + "ci-info": { + "version": "1.4.0", + "bundled": true + }, + "cidr-regex": { + "version": "2.0.9", + "bundled": true, + "requires": { + "ip-regex": "^2.1.0" + } + }, + "cli-boxes": { + "version": "1.0.0", + "bundled": true + }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "requires": { + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + } + }, + "cli-table3": { + "version": "0.5.0", + "bundled": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "clone": { + "version": "1.0.4", + "bundled": true + }, + "cmd-shim": { + "version": "2.0.2", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" + } + }, + "co": { + "version": "4.6.0", + "bundled": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "color-convert": { + "version": "1.9.1", + "bundled": true, + "requires": { + "color-name": "^1.1.1" + } + }, + "color-name": { + "version": "1.1.3", + "bundled": true + }, + "colors": { + "version": "1.1.2", + "bundled": true, + "optional": true + }, + "columnify": { + "version": "1.5.4", + "bundled": true, + "requires": { + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" + } + }, + "combined-stream": { + "version": "1.0.6", + "bundled": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "concat-stream": { + "version": "1.6.2", + "bundled": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "config-chain": { + "version": "1.1.11", + "bundled": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "configstore": { + "version": "3.1.2", + "bundled": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "copy-concurrently": { + "version": "1.0.5", + "bundled": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "create-error-class": { + "version": "3.0.2", + "bundled": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "bundled": true + }, + "cyclist": { + "version": "0.2.2", + "bundled": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true + }, + "defaults": { + "version": "1.0.3", + "bundled": true, + "requires": { + "clone": "^1.0.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "detect-indent": { + "version": "5.0.0", + "bundled": true + }, + "detect-newline": { + "version": "2.1.0", + "bundled": true + }, + "dezalgo": { + "version": "1.0.3", + "bundled": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "bundled": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotenv": { + "version": "5.0.1", + "bundled": true + }, + "duplexer3": { + "version": "0.1.4", + "bundled": true + }, + "duplexify": { + "version": "3.6.0", + "bundled": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "editor": { + "version": "1.0.0", + "bundled": true + }, + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "requires": { + "once": "^1.4.0" + } + }, + "err-code": { + "version": "1.1.2", + "bundled": true + }, + "errno": { + "version": "0.1.7", + "bundled": true, + "requires": { + "prr": "~1.0.1" + } + }, + "es6-promise": { + "version": "4.2.4", + "bundled": true + }, + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "extend": { + "version": "3.0.2", + "bundled": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "bundled": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true + }, + "figgy-pudding": { + "version": "3.4.1", + "bundled": true + }, + "find-npm-prefix": { + "version": "1.0.2", + "bundled": true + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + } + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true + }, + "form-data": { + "version": "2.3.2", + "bundled": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs-vacuum": { + "version": "1.2.10", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "path-is-inside": "^1.0.1", + "rimraf": "^2.5.2" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "genfun": { + "version": "4.0.1", + "bundled": true + }, + "gentle-fs": { + "version": "2.0.1", + "bundled": true, + "requires": { + "aproba": "^1.1.2", + "fs-vacuum": "^1.2.10", + "graceful-fs": "^4.1.11", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "path-is-inside": "^1.0.2", + "read-cmd-shim": "^1.0.1", + "slide": "^1.1.6" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true + } + } + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global-dirs": { + "version": "0.1.1", + "bundled": true, + "requires": { + "ini": "^1.3.4" + } + }, + "got": { + "version": "6.7.1", + "bundled": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true + }, + "har-schema": { + "version": "2.0.0", + "bundled": true + }, + "har-validator": { + "version": "5.1.0", + "bundled": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "hosted-git-info": { + "version": "2.7.1", + "bundled": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "bundled": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + } + }, + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "bundled": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "iferr": { + "version": "1.0.2", + "bundled": true + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "import-lazy": { + "version": "2.1.0", + "bundled": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "init-package-json": { + "version": "1.10.3", + "bundled": true, + "requires": { + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true + }, + "ip": { + "version": "1.1.5", + "bundled": true + }, + "ip-regex": { + "version": "2.1.0", + "bundled": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-ci": { + "version": "1.1.0", + "bundled": true, + "requires": { + "ci-info": "^1.0.0" + } + }, + "is-cidr": { + "version": "2.0.6", + "bundled": true, + "requires": { + "cidr-regex": "^2.0.8" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "bundled": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "bundled": true + }, + "is-obj": { + "version": "1.0.1", + "bundled": true + }, + "is-path-inside": { + "version": "1.0.1", + "bundled": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-redirect": { + "version": "1.0.0", + "bundled": true + }, + "is-retry-allowed": { + "version": "1.1.0", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "latest-version": { + "version": "3.1.0", + "bundled": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "lazy-property": { + "version": "1.0.0", + "bundled": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "libcipm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "graceful-fs": "^4.1.11", + "lock-verify": "^2.0.2", + "mkdirp": "^0.5.1", + "npm-lifecycle": "^2.0.3", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "pacote": "^8.1.6", + "protoduck": "^5.0.0", + "read-package-json": "^2.0.13", + "rimraf": "^2.6.2", + "worker-farm": "^1.6.0" + } + }, + "libnpmhook": { + "version": "4.0.1", + "bundled": true, + "requires": { + "figgy-pudding": "^3.1.0", + "npm-registry-fetch": "^3.0.0" + }, + "dependencies": { + "npm-registry-fetch": { + "version": "3.1.1", + "bundled": true, + "requires": { + "bluebird": "^3.5.1", + "figgy-pudding": "^3.1.0", + "lru-cache": "^4.1.2", + "make-fetch-happen": "^4.0.0", + "npm-package-arg": "^6.0.0" + } + } + } + }, + "libnpx": { + "version": "10.2.0", + "bundled": true, + "requires": { + "dotenv": "^5.0.1", + "npm-package-arg": "^6.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.0", + "update-notifier": "^2.3.0", + "which": "^1.3.0", + "y18n": "^4.0.0", + "yargs": "^11.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lock-verify": { + "version": "2.0.2", + "bundled": true, + "requires": { + "npm-package-arg": "^5.1.2 || 6", + "semver": "^5.4.1" + } + }, + "lockfile": { + "version": "1.0.4", + "bundled": true, + "requires": { + "signal-exit": "^3.0.2" + } + }, + "lodash._baseindexof": { + "version": "3.1.0", + "bundled": true + }, + "lodash._baseuniq": { + "version": "4.6.0", + "bundled": true, + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "bundled": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "bundled": true + }, + "lodash._createcache": { + "version": "3.1.2", + "bundled": true, + "requires": { + "lodash._getnative": "^3.0.0" + } + }, + "lodash._createset": { + "version": "4.0.3", + "bundled": true + }, + "lodash._getnative": { + "version": "3.9.1", + "bundled": true + }, + "lodash._root": { + "version": "3.0.1", + "bundled": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "bundled": true + }, + "lodash.restparam": { + "version": "3.6.1", + "bundled": true + }, + "lodash.union": { + "version": "4.6.0", + "bundled": true + }, + "lodash.uniq": { + "version": "4.5.0", + "bundled": true + }, + "lodash.without": { + "version": "4.4.0", + "bundled": true + }, + "lowercase-keys": { + "version": "1.0.1", + "bundled": true + }, + "lru-cache": { + "version": "4.1.3", + "bundled": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "requires": { + "pify": "^3.0.0" + } + }, + "make-fetch-happen": { + "version": "4.0.1", + "bundled": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^11.0.1", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "lru-cache": "^4.1.2", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + } + }, + "meant": { + "version": "1.0.1", + "bundled": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "mime-db": { + "version": "1.35.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.19", + "bundled": true, + "requires": { + "mime-db": "~1.35.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "minipass": { + "version": "2.3.3", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mississippi": { + "version": "3.0.0", + "bundled": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "move-concurrently": { + "version": "1.0.1", + "bundled": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true + }, + "mute-stream": { + "version": "0.0.7", + "bundled": true + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-gyp": { + "version": "3.8.0", + "bundled": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "nopt": { + "version": "3.0.6", + "bundled": true, + "requires": { + "abbrev": "1" + } + }, + "semver": { + "version": "5.3.0", + "bundled": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + } + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-audit-report": { + "version": "1.3.1", + "bundled": true, + "requires": { + "cli-table3": "^0.5.0", + "console-control-strings": "^1.1.0" + } + }, + "npm-bundled": { + "version": "1.0.5", + "bundled": true + }, + "npm-cache-filename": { + "version": "1.0.2", + "bundled": true + }, + "npm-install-checks": { + "version": "3.0.0", + "bundled": true, + "requires": { + "semver": "^2.3.0 || 3.x || 4 || 5" + } + }, + "npm-lifecycle": { + "version": "2.1.0", + "bundled": true, + "requires": { + "byline": "^5.0.0", + "graceful-fs": "^4.1.11", + "node-gyp": "^3.8.0", + "resolve-from": "^4.0.0", + "slide": "^1.1.6", + "uid-number": "0.0.6", + "umask": "^1.1.0", + "which": "^1.3.1" + } + }, + "npm-logical-tree": { + "version": "1.2.1", + "bundled": true + }, + "npm-package-arg": { + "version": "6.1.0", + "bundled": true, + "requires": { + "hosted-git-info": "^2.6.0", + "osenv": "^0.1.5", + "semver": "^5.5.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "1.1.11", + "bundled": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "2.1.0", + "bundled": true, + "requires": { + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "npm-profile": { + "version": "3.0.2", + "bundled": true, + "requires": { + "aproba": "^1.1.2 || 2", + "make-fetch-happen": "^2.5.0 || 3 || 4" + } + }, + "npm-registry-client": { + "version": "8.6.0", + "bundled": true, + "requires": { + "concat-stream": "^1.5.2", + "graceful-fs": "^4.1.6", + "normalize-package-data": "~1.0.1 || ^2.0.0", + "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "npmlog": "2 || ^3.1.0 || ^4.0.0", + "once": "^1.3.3", + "request": "^2.74.0", + "retry": "^0.10.0", + "safe-buffer": "^5.1.1", + "semver": "2 >=2.2.1 || 3.x || 4 || 5", + "slide": "^1.1.3", + "ssri": "^5.2.4" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "bundled": true + }, + "ssri": { + "version": "5.3.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.1" + } + } + } + }, + "npm-registry-fetch": { + "version": "1.1.0", + "bundled": true, + "requires": { + "bluebird": "^3.5.1", + "figgy-pudding": "^2.0.1", + "lru-cache": "^4.1.2", + "make-fetch-happen": "^3.0.0", + "npm-package-arg": "^6.0.0", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "cacache": { + "version": "10.0.4", + "bundled": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + }, + "dependencies": { + "mississippi": { + "version": "2.0.0", + "bundled": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + } + } + }, + "figgy-pudding": { + "version": "2.0.1", + "bundled": true + }, + "make-fetch-happen": { + "version": "3.0.0", + "bundled": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^10.0.4", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.0", + "lru-cache": "^4.1.2", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^3.0.1", + "ssri": "^5.2.4" + } + }, + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "smart-buffer": { + "version": "1.1.15", + "bundled": true + }, + "socks": { + "version": "1.1.10", + "bundled": true, + "requires": { + "ip": "^1.1.4", + "smart-buffer": "^1.0.13" + } + }, + "socks-proxy-agent": { + "version": "3.0.1", + "bundled": true, + "requires": { + "agent-base": "^4.1.0", + "socks": "^1.1.10" + } + }, + "ssri": { + "version": "5.3.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.1" + } + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npm-user-validate": { + "version": "1.0.0", + "bundled": true + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "oauth-sign": { + "version": "0.9.0", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.5.0", + "bundled": true + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true + }, + "package-json": { + "version": "4.0.1", + "bundled": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pacote": { + "version": "8.1.6", + "bundled": true, + "requires": { + "bluebird": "^3.5.1", + "cacache": "^11.0.2", + "get-stream": "^3.0.0", + "glob": "^7.1.2", + "lru-cache": "^4.1.3", + "make-fetch-happen": "^4.0.1", + "minimatch": "^3.0.4", + "minipass": "^2.3.3", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.10", + "npm-pick-manifest": "^2.1.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.5.0", + "ssri": "^6.0.0", + "tar": "^4.4.3", + "unique-filename": "^1.1.0", + "which": "^1.3.0" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "path-is-inside": { + "version": "1.0.2", + "bundled": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true + }, + "performance-now": { + "version": "2.1.0", + "bundled": true + }, + "pify": { + "version": "3.0.0", + "bundled": true + }, + "prepend-http": { + "version": "1.0.4", + "bundled": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "promzard": { + "version": "0.3.0", + "bundled": true, + "requires": { + "read": "1" + } + }, + "proto-list": { + "version": "1.2.4", + "bundled": true + }, + "protoduck": { + "version": "5.0.0", + "bundled": true, + "requires": { + "genfun": "^4.0.1" + } + }, + "prr": { + "version": "1.0.1", + "bundled": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true + }, + "psl": { + "version": "1.1.29", + "bundled": true + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "bundled": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "1.4.1", + "bundled": true + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true + }, + "qs": { + "version": "6.5.2", + "bundled": true + }, + "query-string": { + "version": "6.1.0", + "bundled": true, + "requires": { + "decode-uri-component": "^0.2.0", + "strict-uri-encode": "^2.0.0" + } + }, + "qw": { + "version": "1.0.1", + "bundled": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true + } + } + }, + "read": { + "version": "1.0.7", + "bundled": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-cmd-shim": { + "version": "1.0.1", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "read-installed": { + "version": "4.0.3", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + } + }, + "read-package-json": { + "version": "2.0.13", + "bundled": true, + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "slash": "^1.0.0" + } + }, + "read-package-tree": { + "version": "5.2.1", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "once": "^1.3.0", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.0.2", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "registry-auth-token": { + "version": "3.3.2", + "bundled": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "bundled": true, + "requires": { + "rc": "^1.0.1" + } + }, + "request": { + "version": "2.88.0", + "bundled": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true + }, + "retry": { + "version": "0.12.0", + "bundled": true + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "requires": { + "glob": "^7.0.5" + } + }, + "run-queue": { + "version": "1.0.3", + "bundled": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "semver": { + "version": "5.5.0", + "bundled": true + }, + "semver-diff": { + "version": "2.1.0", + "bundled": true, + "requires": { + "semver": "^5.0.3" + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "sha": { + "version": "2.0.1", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "readable-stream": "^2.0.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "slash": { + "version": "1.0.0", + "bundled": true + }, + "slide": { + "version": "1.1.6", + "bundled": true + }, + "smart-buffer": { + "version": "4.0.1", + "bundled": true + }, + "socks": { + "version": "2.2.0", + "bundled": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.0.1" + } + }, + "socks-proxy-agent": { + "version": "4.0.1", + "bundled": true, + "requires": { + "agent-base": "~4.2.0", + "socks": "~2.2.0" + } + }, + "sorted-object": { + "version": "2.0.1", + "bundled": true + }, + "sorted-union-stream": { + "version": "2.1.3", + "bundled": true, + "requires": { + "from2": "^1.3.0", + "stream-iterate": "^1.1.0" + }, + "dependencies": { + "from2": { + "version": "1.3.0", + "bundled": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.10" + } + }, + "isarray": { + "version": "0.0.1", + "bundled": true + }, + "readable-stream": { + "version": "1.1.14", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + } + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true + }, + "sshpk": { + "version": "1.14.2", + "bundled": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.0", + "bundled": true + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-iterate": { + "version": "1.2.0", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "stream-shift": "^1.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "bundled": true + }, + "strict-uri-encode": { + "version": "2.0.0", + "bundled": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "stringify-package": { + "version": "1.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tar": { + "version": "4.4.6", + "bundled": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.3", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + }, + "dependencies": { + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "term-size": { + "version": "1.2.0", + "bundled": true, + "requires": { + "execa": "^0.7.0" + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true + }, + "through": { + "version": "2.3.8", + "bundled": true + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + }, + "timed-out": { + "version": "4.0.1", + "bundled": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true + }, + "tough-cookie": { + "version": "2.4.3", + "bundled": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "bundled": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true + }, + "umask": { + "version": "1.1.0", + "bundled": true + }, + "unique-filename": { + "version": "1.1.0", + "bundled": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.0", + "bundled": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "bundled": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "bundled": true + }, + "unzip-response": { + "version": "2.0.1", + "bundled": true + }, + "update-notifier": { + "version": "2.5.0", + "bundled": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "bundled": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "util-extend": { + "version": "1.0.3", + "bundled": true + }, + "uuid": { + "version": "3.3.2", + "bundled": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "bundled": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "which": { + "version": "1.3.1", + "bundled": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "requires": { + "string-width": "^1.0.2" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "widest-line": { + "version": "2.0.0", + "bundled": true, + "requires": { + "string-width": "^2.1.1" + } + }, + "worker-farm": { + "version": "1.6.0", + "bundled": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "write-file-atomic": { + "version": "2.3.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "bundled": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true + }, + "y18n": { + "version": "4.0.0", + "bundled": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true + }, + "yargs": { + "version": "11.0.0", + "bundled": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + }, + "dependencies": { + "y18n": { + "version": "3.2.1", + "bundled": true + } + } + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -9281,10 +12982,13 @@ } }, "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } }, "open": { "version": "0.0.5", @@ -9300,6 +13004,14 @@ "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } } }, "optionator": { @@ -9314,14 +13026,6 @@ "prelude-ls": "~1.1.2", "type-check": "~0.3.2", "wordwrap": "~1.0.0" - }, - "dependencies": { - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - } } }, "optipng-bin": { @@ -9385,9 +13089,9 @@ "dev": true }, "pac-proxy-agent": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-2.0.2.tgz", - "integrity": "sha512-cDNAN1Ehjbf5EHkNY5qnRhGPUCp6SnpyVof5fRzN800QV1Y2OkzbH9rmjZkbBRa8igof903yOnjIl6z0SlAhxA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-3.0.0.tgz", + "integrity": "sha512-AOUX9jES/EkQX2zRz0AW7lSx9jD//hQS8wFXBvcnd/J2Py9KaMJMqV/LPqJssj1tgGufotb2mmopGPR15ODv1Q==", "dev": true, "optional": true, "requires": { @@ -9398,7 +13102,7 @@ "https-proxy-agent": "^2.2.1", "pac-resolver": "^3.0.0", "raw-body": "^2.2.0", - "socks-proxy-agent": "^3.0.0" + "socks-proxy-agent": "^4.0.1" }, "dependencies": { "bytes": { @@ -9409,13 +13113,13 @@ "optional": true }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", "dev": true, "optional": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "depd": { @@ -9427,7 +13131,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "optional": true, @@ -9441,17 +13145,24 @@ "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "integrity": "sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM=", "dev": true, "optional": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true, + "optional": true + }, "raw-body": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "integrity": "sha1-GzJOzmtXBuFThVvBFIxlu39uoMM=", "dev": true, "optional": true, "requires": { @@ -9460,24 +13171,13 @@ "iconv-lite": "0.4.23", "unpipe": "1.0.0" } - }, - "socks-proxy-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz", - "integrity": "sha512-ZwEDymm204mTzvdqyUqOdovVr2YRd2NYskrYrF2LXyZ9qDiMAoFESGK8CRphiO7rtbo2Y757k2Nia3x2hGtalA==", - "dev": true, - "optional": true, - "requires": { - "agent-base": "^4.1.0", - "socks": "^1.1.10" - } } } }, "pac-resolver": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz", - "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==", + "integrity": "sha1-auoweH2wqJFwTet4AKcip2FabyY=", "dev": true, "optional": true, "requires": { @@ -9486,6 +13186,15 @@ "ip": "^1.1.5", "netmask": "^1.0.6", "thunkify": "^2.1.2" + }, + "dependencies": { + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true, + "optional": true + } } }, "parse-filepath": { @@ -9613,9 +13322,9 @@ "dev": true }, "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "path-proxy": { @@ -9679,7 +13388,7 @@ }, "pause-stream": { "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { @@ -9716,9 +13425,15 @@ }, "dependencies": { "es6-promise": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", - "integrity": "sha1-3EIhwrFlGHYL2MOaUtjzVvwA7Sk=", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", + "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", + "dev": true + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", "dev": true } } @@ -9745,55 +13460,15 @@ } }, "plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", "dev": true, "requires": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "dependencies": { - "arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - } - }, - "arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", - "dev": true - }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true - }, - "extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", - "dev": true, - "requires": { - "kind-of": "^1.1.0" - } - }, - "kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", - "dev": true - } + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" } }, "plugin-log": { @@ -9830,7 +13505,7 @@ "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=", "dev": true }, "posix-character-classes": { @@ -9853,7 +13528,7 @@ }, "postcss-calc": { "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", + "resolved": "http://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", "dev": true, "requires": { @@ -9885,7 +13560,7 @@ }, "postcss-discard-comments": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", + "resolved": "http://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", "dev": true, "requires": { @@ -9903,7 +13578,7 @@ }, "postcss-discard-empty": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", "dev": true, "requires": { @@ -9912,7 +13587,7 @@ }, "postcss-discard-overridden": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", + "resolved": "http://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", "dev": true, "requires": { @@ -9921,7 +13596,7 @@ }, "postcss-discard-unused": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", + "resolved": "http://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", "dev": true, "requires": { @@ -9930,13 +13605,12 @@ } }, "postcss-filter-plugins": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz", - "integrity": "sha1-bYWGJTTXNaxCDkqFgG4fXUKG2Ew=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz", + "integrity": "sha512-T53GVFsdinJhgwm7rg1BzbeBRomOg9y5MBVhGcsV0CxurUdVj1UlPdKtn7aqYA/c/QVkzKMjq2bSV5dKG5+AwQ==", "dev": true, "requires": { - "postcss": "^5.0.4", - "uniqid": "^4.0.0" + "postcss": "^5.0.4" } }, "postcss-load-config": { @@ -9973,7 +13647,7 @@ }, "postcss-merge-idents": { "version": "2.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", + "resolved": "http://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", "dev": true, "requires": { @@ -10012,7 +13686,7 @@ }, "postcss-minify-font-values": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", + "resolved": "http://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", "dev": true, "requires": { @@ -10023,7 +13697,7 @@ }, "postcss-minify-gradients": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", + "resolved": "http://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", "dev": true, "requires": { @@ -10033,7 +13707,7 @@ }, "postcss-minify-params": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", + "resolved": "http://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", "dev": true, "requires": { @@ -10045,7 +13719,7 @@ }, "postcss-minify-selectors": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", + "resolved": "http://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", "dev": true, "requires": { @@ -10057,7 +13731,7 @@ }, "postcss-normalize-charset": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", "dev": true, "requires": { @@ -10066,7 +13740,7 @@ }, "postcss-normalize-url": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", + "resolved": "http://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", "dev": true, "requires": { @@ -10088,7 +13762,7 @@ }, "postcss-reduce-idents": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", + "resolved": "http://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", "dev": true, "requires": { @@ -10098,7 +13772,7 @@ }, "postcss-reduce-initial": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", "dev": true, "requires": { @@ -10107,7 +13781,7 @@ }, "postcss-reduce-transforms": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", + "resolved": "http://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", "dev": true, "requires": { @@ -10129,7 +13803,7 @@ }, "postcss-svgo": { "version": "2.1.6", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", + "resolved": "http://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", "dev": true, "requires": { @@ -10141,7 +13815,7 @@ }, "postcss-unique-selectors": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", + "resolved": "http://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", "dev": true, "requires": { @@ -10158,7 +13832,7 @@ }, "postcss-zindex": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", "dev": true, "requires": { @@ -10197,6 +13871,12 @@ "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -10204,9 +13884,9 @@ "dev": true }, "progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", "dev": true }, "promise": { @@ -10230,9 +13910,9 @@ } }, "proxy-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.0.1.tgz", - "integrity": "sha512-mAZexaz9ZxQhYPWfAjzlrloEjW+JHiBFryE4AJXFDTnaXfmH/FKqC1swTRKuEPbHWz02flQNXFOyDUF7zfEG6A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.0.3.tgz", + "integrity": "sha512-PXVVVuH9tiQuxQltFJVSnXWuDtNr+8aNBP6XVDDCDiUuDN8eRCm+ii4/mFWmXWEA0w8jjJSlePa4LXlM4jIzNA==", "dev": true, "optional": true, "requires": { @@ -10241,31 +13921,38 @@ "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.1", "lru-cache": "^4.1.2", - "pac-proxy-agent": "^2.0.1", + "pac-proxy-agent": "^3.0.0", "proxy-from-env": "^1.0.0", "socks-proxy-agent": "^4.0.1" }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", "dev": true, "optional": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "lru-cache": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "integrity": "sha1-oRdc80lt/IQ2wVbDNLSVWZK85pw=", "dev": true, "optional": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true, + "optional": true } } }, @@ -10291,9 +13978,9 @@ "optional": true }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, "q": { @@ -10305,7 +13992,7 @@ "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", "dev": true }, "qs": { @@ -10331,9 +14018,9 @@ "dev": true }, "randomatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", - "integrity": "sha1-01SQAw6091eN4pLObfsEqRoSiSM=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz", + "integrity": "sha512-KnGPVE0lo2WoXxIZ7cPR8YBpiol4gsSuOwDSg410oHh80ZMp5EiypNqL2K4Z77vJn6lB5rap7IkAmcUlalcnBQ==", "dev": true, "requires": { "is-number": "^4.0.0", @@ -10344,7 +14031,7 @@ "is-number": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha1-ACbjf1RU1z41bf5lZGmYZ8an8P8=", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", "dev": true } } @@ -10374,7 +14061,7 @@ }, "iconv-lite": { "version": "0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", "dev": true } @@ -10418,26 +14105,26 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -10465,7 +14152,7 @@ }, "readable-stream": { "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { @@ -10476,15 +14163,14 @@ } }, "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "minimatch": "^3.0.2", - "readable-stream": "^2.0.2", - "set-immediate-shim": "^1.0.1" + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" }, "dependencies": { "graceful-fs": { @@ -10501,8 +14187,8 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -10547,7 +14233,7 @@ "redis": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", - "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", + "integrity": "sha1-ICKI4/WMSfYHnZevehDhMDrhSwI=", "dev": true, "optional": true, "requires": { @@ -10559,7 +14245,7 @@ "redis-commands": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz", - "integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA==", + "integrity": "sha1-RJWIlBTx6IYmEYCxRC5ylWAtg6I=", "dev": true, "optional": true }, @@ -10572,7 +14258,7 @@ }, "reduce-css-calc": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "resolved": "http://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", "dev": true, "requires": { @@ -10606,6 +14292,30 @@ } } }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz", + "integrity": "sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-transform": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.3.tgz", + "integrity": "sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA==", + "dev": true, + "requires": { + "private": "^0.1.6" + } + }, "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", @@ -10625,21 +14335,49 @@ "safe-regex": "^1.1.0" } }, - "regexp.prototype.flags": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", - "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", - "dev": true, - "requires": { - "define-properties": "^1.1.2" - } - }, "regexpp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz", - "integrity": "sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==", + "integrity": "sha1-sqdTSoXKGwM7z1zp/45W1OB1U2U=", "dev": true }, + "regexpu-core": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.2.0.tgz", + "integrity": "sha512-Z835VSnJJ46CNBttalHD/dB+Sj2ezmY6Xp38npwU87peK6mqOzOpV8eYktdkLTEkzzD+JsTcxd84ozd8I14+rw==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^7.0.0", + "regjsgen": "^0.4.0", + "regjsparser": "^0.3.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.0.2" + } + }, + "regjsgen": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.4.0.tgz", + "integrity": "sha512-X51Lte1gCYUdlwhF28+2YMO0U6WeN0GLpgpA7LK7mbdDnkQYiwvEpmpe0F/cv5L14EbxgrdayAG3JETBv0dbXA==", + "dev": true + }, + "regjsparser": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.3.0.tgz", + "integrity": "sha512-zza72oZBBHzt64G7DxdqrOo/30bhHkwMUoT0WqfGu98XLd7N+1tsy5MJ96Bk4MD0y74n629RhmrGW6XlnLLwCA==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -10647,9 +14385,9 @@ "dev": true }, "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", "dev": true }, "repeat-string": { @@ -10708,6 +14446,15 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } } } }, @@ -10723,7 +14470,7 @@ "requestretry": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-1.13.0.tgz", - "integrity": "sha512-Lmh9qMvnQXADGAQxsXHP4rbgO6pffCfuR8XUBdP9aitJcLQJxhp7YZK4xAVYXnPJ5E52mwrfiKQtKonPL8xsmg==", + "integrity": "sha1-IT7BAG7rdQ6LjOVBdig9FajVXZQ=", "dev": true, "optional": true, "requires": { @@ -10756,9 +14503,9 @@ "dev": true }, "resolve": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", - "integrity": "sha1-qt1lY3T9KYruiVvAJrgpdBhnf9M=", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", "dev": true, "requires": { "path-parse": "^1.0.5" @@ -10812,17 +14559,6 @@ "requires": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" - }, - "dependencies": { - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - } } }, "ret": { @@ -10873,15 +14609,67 @@ "chalk": "^1.1.3", "fancy-log": "^1.3.2", "plugin-error": "^0.1.2" + }, + "dependencies": { + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "^1.1.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + } + } } }, "rxjs": { - "version": "5.5.11", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", - "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", "dev": true, "requires": { - "symbol-observable": "1.0.1" + "tslib": "^1.9.0" } }, "safe-buffer": { @@ -10902,7 +14690,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", "dev": true }, "sax": { @@ -10944,9 +14732,9 @@ }, "dependencies": { "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha1-3Eu8emyp2Rbe5dQ1FvAJK1j3uKs=", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", "dev": true, "optional": true } @@ -10974,7 +14762,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -11044,7 +14832,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -11102,7 +14890,7 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=", "dev": true }, "shebang-command": { @@ -11151,18 +14939,17 @@ "slice-ansi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "integrity": "sha1-BE8aSdiEL/MHqta1Be0Xi9lQE00=", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0" } }, "smart-buffer": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", - "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=", - "dev": true, - "optional": true + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.1.tgz", + "integrity": "sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg==", + "dev": true }, "smtp-connection": { "version": "2.12.0", @@ -11333,7 +15120,7 @@ "socket.io-parser": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.3.tgz", - "integrity": "sha512-g0a2HPqLguqAczs3dMECuA1RgoGFPyvDqcbaDEdCWY9g59kdUAz3YRmaJBNKXflrHNwB7Q12Gkf/0CZXfdHR7g==", + "integrity": "sha1-7S2l7nnxCVUDbj2kE7/X8eTYbI4=", "dev": true, "requires": { "component-emitter": "1.2.1", @@ -11345,7 +15132,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -11360,45 +15147,23 @@ } }, "socks": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz", - "integrity": "sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.1.tgz", + "integrity": "sha512-0GabKw7n9mI46vcNrVfs0o6XzWzjVa3h6GaSo2UPxtWAROXUWavfJWh1M4PR5tnE0dcnQXZIDFP4yrAysLze/w==", "dev": true, - "optional": true, "requires": { - "ip": "^1.1.4", - "smart-buffer": "^1.0.13" + "ip": "^1.1.5", + "smart-buffer": "^4.0.1" } }, "socks-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", - "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", + "integrity": "sha1-WTa/i3B6mTB5xvN9sgkYIb/6ZHM=", "dev": true, - "optional": true, "requires": { "agent-base": "~4.2.0", "socks": "~2.2.0" - }, - "dependencies": { - "smart-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.1.tgz", - "integrity": "sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg==", - "dev": true, - "optional": true - }, - "socks": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.1.tgz", - "integrity": "sha512-0GabKw7n9mI46vcNrVfs0o6XzWzjVa3h6GaSo2UPxtWAROXUWavfJWh1M4PR5tnE0dcnQXZIDFP4yrAysLze/w==", - "dev": true, - "optional": true, - "requires": { - "ip": "^1.1.5", - "smart-buffer": "^4.0.1" - } - } } }, "sort-keys": { @@ -11417,12 +15182,12 @@ "dev": true }, "source-map-resolve": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", - "integrity": "sha1-etD1k/IoFZjoVN+A8ZquS5LXoRo=", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", "dev": true, "requires": { - "atob": "^2.0.0", + "atob": "^2.1.1", "decode-uri-component": "^0.2.0", "resolve-url": "^0.2.1", "source-map-url": "^0.4.0", @@ -11436,15 +15201,15 @@ "dev": true }, "sparkles": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", - "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", "dev": true }, "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha1-BaW01xU6GVvJLDxCW2nzsqlSTII=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.1.tgz", + "integrity": "sha512-hxSPZbRZvSDuOvADntOElzJpenIR7wXJkuoUcUtS0erbgt2fgeaoPIYretfKpslMhfFDY4k0MZ2F5CUzhBsSvQ==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -11452,9 +15217,9 @@ } }, "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha1-LHrmEFbHFKW5ubKyr30xHvXHj+k=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", "dev": true }, "spdx-expression-parse": { @@ -11468,15 +15233,15 @@ } }, "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha1-enzShHDMbToc/m1miG9rxDDTrIc=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz", + "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==", "dev": true }, "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "dev": true, "requires": { "through": "2" @@ -11510,9 +15275,9 @@ } }, "sshpk": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", - "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "dev": true, "requires": { "asn1": "~0.2.3", @@ -11522,6 +15287,7 @@ "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, "dependencies": { @@ -11574,12 +15340,13 @@ "dev": true }, "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "version": "0.2.2", + "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", "dev": true, "requires": { - "duplexer": "~0.1.1" + "duplexer": "~0.1.1", + "through": "~2.3.4" } }, "stream-combiner2": { @@ -11609,26 +15376,26 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -11657,7 +15424,7 @@ "streamroller": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", + "integrity": "sha1-odG3z4PTmvsNYwSaWsv5NJO99ks=", "dev": true, "requires": { "date-format": "^1.2.0", @@ -11667,12 +15434,12 @@ }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "isarray": { @@ -11681,9 +15448,15 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -11699,7 +15472,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -11716,7 +15489,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -11740,19 +15513,6 @@ } } }, - "string.prototype.matchall": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-2.0.0.tgz", - "integrity": "sha512-WoZ+B2ypng1dp4iFLF2kmZlwwlE19gmjgKuhL1FJfDgCREWb3ye3SDVHSzLH6bxfnvYmkCxbzkmWcQZHA4P//Q==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.10.0", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "regexp.prototype.flags": "^1.2.0" - } - }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -11760,14 +15520,14 @@ "dev": true }, "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", "dev": true }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -11785,54 +15545,15 @@ } }, "strip-bom-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", - "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", + "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", "dev": true, "requires": { - "first-chunk-stream": "^2.0.0", + "first-chunk-stream": "^1.0.0", "strip-bom": "^2.0.0" }, "dependencies": { - "first-chunk-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", - "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", @@ -11846,7 +15567,7 @@ }, "strip-dirs": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz", "integrity": "sha1-lgu9EoeETzl1pFWKoQOoJV4kVqA=", "dev": true, "requires": { @@ -11875,7 +15596,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -11953,15 +15674,9 @@ } } }, - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", - "dev": true - }, "table": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", + "resolved": "http://registry.npmjs.org/table/-/table-4.0.3.tgz", "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", "dev": true, "requires": { @@ -11973,18 +15688,6 @@ "string-width": "^2.1.1" }, "dependencies": { - "ajv": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", - "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.1" - } - }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -12012,9 +15715,9 @@ "dev": true }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -12023,17 +15726,17 @@ } }, "tar-stream": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.1.tgz", - "integrity": "sha1-+E7xaWJp1iI8pI9uHu7eP36B85U=", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", "dev": true, "requires": { "bl": "^1.0.0", - "buffer-alloc": "^1.1.0", + "buffer-alloc": "^1.2.0", "end-of-stream": "^1.0.0", "fs-constants": "^1.0.0", "readable-stream": "^2.3.0", - "to-buffer": "^1.1.0", + "to-buffer": "^1.1.1", "xtend": "^4.0.0" }, "dependencies": { @@ -12054,17 +15757,17 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -12073,7 +15776,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -12110,7 +15813,7 @@ }, "through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -12132,8 +15835,8 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -12258,7 +15961,7 @@ }, "debug": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -12273,7 +15976,7 @@ }, "iconv-lite": { "version": "0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", "dev": true }, @@ -12294,7 +15997,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -12332,6 +16035,12 @@ "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", "dev": true }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -12381,6 +16090,14 @@ "dev": true, "requires": { "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } } }, "trim-newlines": { @@ -12398,12 +16115,24 @@ "escape-string-regexp": "^1.0.2" } }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, "tryit": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", "dev": true }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, "tsscmp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.5.tgz", @@ -12411,13 +16140,10 @@ "dev": true }, "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true }, "tweetnacl": { "version": "0.14.5", @@ -12481,7 +16207,7 @@ "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=", "dev": true }, "unc-path-regex": { @@ -12496,6 +16222,34 @@ "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", "dev": true }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz", + "integrity": "sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz", + "integrity": "sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==", + "dev": true + }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", @@ -12537,15 +16291,6 @@ "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", "dev": true }, - "uniqid": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-4.1.1.tgz", - "integrity": "sha1-iSIN32t1GuUrX3JISGNShZa7hME=", - "dev": true, - "requires": { - "macaddress": "^0.2.8" - } - }, "uniqs": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", @@ -12626,24 +16371,16 @@ "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "integrity": "sha1-NSVll+RqWB20eT0M5H+prr/J+r0=", "dev": true }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", "dev": true, "requires": { "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - } } }, "urix": { @@ -12672,13 +16409,10 @@ } }, "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha1-FHFr8D/f79AwQK71jYtLhfOnxUQ=", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true }, "user-home": { "version": "1.1.1", @@ -12728,15 +16462,15 @@ "dev": true }, "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha1-EsUou51Y0LkmXZovbw/ovhf/HxQ=", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", "dev": true }, "uws": { "version": "9.14.0", "resolved": "https://registry.npmjs.org/uws/-/uws-9.14.0.tgz", - "integrity": "sha512-HNMztPP5A1sKuVFmdZ6BPVpBQd5bUjNC8EFMFiICK+oho/OQsAJy5hnIx4btMHiOk8j04f/DbIlqnEZ9d72dqg==", + "integrity": "sha1-+sg4a+/DOno3BcvVjcR7Qwyk3ZU=", "dev": true, "optional": true }, @@ -12756,9 +16490,9 @@ "dev": true }, "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha1-gWQ7y+8b3+zUYjeT3EZIlIupgzg=", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { "spdx-correct": "^3.0.0", @@ -12831,26 +16565,26 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -12878,12 +16612,51 @@ "vinyl": "^1.1.0" }, "dependencies": { + "first-chunk-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", + "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", @@ -12893,6 +16666,16 @@ "is-utf8": "^0.2.0" } }, + "strip-bom-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", + "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", + "dev": true, + "requires": { + "first-chunk-stream": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, "vinyl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", @@ -12930,7 +16713,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -13015,9 +16798,9 @@ "dev": true }, "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { "isexe": "^2.0.0" @@ -13037,9 +16820,9 @@ "optional": true }, "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, "wrap-fn": { @@ -13049,14 +16832,6 @@ "dev": true, "requires": { "co": "3.1.0" - }, - "dependencies": { - "co": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz", - "integrity": "sha1-TqVOpaCJOBUxheFSEMaNkJK8G3g=", - "dev": true - } } }, "wrappy": { @@ -13077,7 +16852,7 @@ "ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", "dev": true, "requires": { "async-limiter": "~1.0.0", @@ -13113,7 +16888,7 @@ }, "yargs": { "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, "requires": { @@ -13121,15 +16896,24 @@ "cliui": "^2.1.0", "decamelize": "^1.0.0", "window-size": "0.1.0" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + } } }, "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, "requires": { - "fd-slicer": "~1.0.1" + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" } }, "yeast": { diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index c4b4ac0350..f7e66f8d24 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -1,30 +1,21 @@ { - "author": "Umbraco HQ", - "name": "umbraco", - "homepage": "https://github.com/umbraco/umbraco-cms/", - "version": "0.0.0", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/umbraco/Umbraco-CMS.git" - }, - "bugs": { - "url": "https://issues.umbraco.org" - }, - "engines": { - "node": ">= 0.8.4" - }, "scripts": { "install": "bower-installer", "test": "karma start test/config/karma.conf.js --singlerun", "build": "gulp" }, - "dependencies": {}, + "dependencies": { + "flatpickr": "4.5.2", + "npm": "^6.4.1" + }, "devDependencies": { + "@babel/core": "^7.1.2", + "@babel/preset-env": "^7.1.0", "autoprefixer": "^6.5.0", "bower-installer": "^1.2.0", "cssnano": "^3.7.6", "gulp": "^3.9.1", + "gulp-babel": "^8.0.0-beta.2", "gulp-concat": "^2.6.0", "gulp-connect": "5.0.0", "gulp-eslint": "^5.0.0", diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/web.config b/src/Umbraco.Web.UI.Client/src/assets/fonts/web.config new file mode 100644 index 0000000000..42051b6de2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/fonts/web.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js index 8c0ec78025..5476404e6a 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js @@ -1,19 +1,19 @@ LazyLoad.js([ - '../lib/jquery/jquery.min.js', - '../lib/angular/1.1.5/angular.min.js', - '../lib/underscore/underscore-min.js', - '../lib/umbraco/Extensions.js', - '../js/app.js', - '../js/umbraco.resources.js', - '../js/umbraco.services.js', - '../js/umbraco.interceptors.js', - '../ServerVariables', - '../lib/signalr/jquery.signalR.js', - '/umbraco/BackOffice/signalr/hubs', - '../js/umbraco.canvasdesigner.js' + '../lib/jquery/jquery.min.js', + '../lib/angular/angular.js', + '../lib/underscore/underscore-min.js', + '../lib/umbraco/Extensions.js', + '../js/app.js', + '../js/umbraco.resources.js', + '../js/umbraco.services.js', + '../js/umbraco.interceptors.js', + '../ServerVariables', + '../lib/signalr/jquery.signalR.js', + '../BackOffice/signalr/hubs', + '../js/umbraco.canvasdesigner.js' ], function () { - jQuery(document).ready(function () { - angular.bootstrap(document, ['Umbraco.canvasdesigner']); - }); + jQuery(document).ready(function () { + angular.bootstrap(document, ['Umbraco.canvasdesigner']); + }); }); diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js deleted file mode 100644 index 975d490086..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js +++ /dev/null @@ -1,117 +0,0 @@ - -/*********************************************************************************************************/ -/* Canvasdesigner panel app and controller */ -/*********************************************************************************************************/ - -var app = angular.module("Umbraco.canvasdesigner", ['umbraco.resources', 'umbraco.services']) - -.controller("Umbraco.canvasdesignerController", function ($scope, $http, $window, $timeout, $location, dialogService) { - - var isInit = $location.search().init; - if (isInit === "true") { - //do not continue, this is the first load of this new window, if this is passed in it means it's been - //initialized by the content editor and then the content editor will actually re-load this window without - //this flag. This is a required trick to get around chrome popup mgr. We don't want to double load preview.aspx - //since that will double prepare the preview documents - return; - } - - $scope.isOpen = false; - $scope.frameLoaded = false; - var pageId = $location.search().id; - $scope.pageId = pageId; - $scope.pageUrl = "../dialogs/Preview.aspx?id=" + pageId; - $scope.valueAreLoaded = false; - $scope.devices = [ - { name: "desktop", css: "desktop", icon: "icon-display", title: "Desktop" }, - { name: "laptop - 1366px", css: "laptop border", icon: "icon-laptop", title: "Laptop" }, - { name: "iPad portrait - 768px", css: "iPad-portrait border", icon: "icon-ipad", title: "Tablet portrait" }, - { name: "iPad landscape - 1024px", css: "iPad-landscape border", icon: "icon-ipad flip", title: "Tablet landscape" }, - { name: "smartphone portrait - 480px", css: "smartphone-portrait border", icon: "icon-iphone", title: "Smartphone portrait" }, - { name: "smartphone landscape - 320px", css: "smartphone-landscape border", icon: "icon-iphone flip", title: "Smartphone landscape" } - ]; - $scope.previewDevice = $scope.devices[0]; - - /*****************************************************************************/ - /* Preview devices */ - /*****************************************************************************/ - - // Set preview device - $scope.updatePreviewDevice = function (device) { - $scope.previewDevice = device; - }; - - /*****************************************************************************/ - /* Exit Preview */ - /*****************************************************************************/ - - $scope.exitPreview = function () { - window.top.location.href = "../endPreview.aspx?redir=%2f" + $scope.pageId; - }; - - - - /*****************************************************************************/ - /* Panel managment */ - /*****************************************************************************/ - - $scope.openPreviewDevice = function () { - $scope.showDevicesPreview = true; - $scope.closeIntelCanvasdesigner(); - } - - /*****************************************************************************/ - /* Call function into the front-end */ - /*****************************************************************************/ - - - var hideUmbracoPreviewBadge = function () { - var iframe = (document.getElementById("resultFrame").contentWindow || document.getElementById("resultFrame").contentDocument); - if(iframe.document.getElementById("umbracoPreviewBadge")) - iframe.document.getElementById("umbracoPreviewBadge").style.display = "none"; - }; - - /*****************************************************************************/ - /* Init */ - /*****************************************************************************/ - - - // signalr hub - var previewHub = $.connection.previewHub; - - previewHub.client.refreshed = function (message, sender) { - console.log("Notified by SignalR preview hub (" + message+ ")."); - - if ($scope.pageId != message) { - console.log("Not a notification for us (" + $scope.pageId + ")."); - return; - } - - var resultFrame = document.getElementById("resultFrame"); - var iframe = (resultFrame.contentWindow || resultFrame.contentDocument); - //setTimeout(function() { iframe.location.reload(); }, 1000); - iframe.location.reload(); - }; - - $.connection.hub.start() - .done(function () { console.log("Connected to SignalR preview hub (ID=" + $.connection.hub.id + ")"); }) - .fail(function () { console.log("Could not connect to SignalR preview hub."); }); -}) - - -.directive('iframeIsLoaded', function ($timeout) { - return { - restrict: 'A', - link: function (scope, element, attr) { - element.load(function () { - var iframe = (element.context.contentWindow || element.context.contentDocument); - if(iframe && iframe.document.getElementById("umbracoPreviewBadge")) - iframe.document.getElementById("umbracoPreviewBadge").style.display = "none"; - if (!document.getElementById("resultFrame").contentWindow.refreshLayout) { - scope.frameLoaded = true; - scope.$apply(); - } - }); - } - }; -}) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js index 461101a66c..3ddb7af0e1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js @@ -55,6 +55,11 @@ }); })); + scope.searchClick = function() { + var showSearch = appState.getSearchState("show"); + appState.setSearchState("show", !showSearch); + }; + // toggle the help dialog by raising the global app state to toggle the help drawer scope.helpClick = function () { var showDrawer = appState.getDrawerState("showDrawer"); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js new file mode 100644 index 0000000000..7ff76d5670 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js @@ -0,0 +1,118 @@ +(function () { + 'use strict'; + + /** + * A component to render the pop up search field + */ + var umbSearch = { + templateUrl: 'views/components/application/umb-search.html', + controllerAs: 'vm', + controller: umbSearchController, + bindings: { + onClose: "&" + } + }; + + function umbSearchController($timeout, backdropService, searchService) { + + var vm = this; + + vm.$onInit = onInit; + vm.$onDestroy = onDestroy; + vm.search = search; + vm.clickItem = clickItem; + vm.clearSearch = clearSearch; + vm.handleKeyUp = handleKeyUp; + vm.closeSearch = closeSearch; + vm.focusSearch = focusSearch; + + function onInit() { + vm.searchQuery = ""; + vm.searchResults = []; + vm.hasResults = false; + focusSearch(); + backdropService.open(); + } + + function onDestroy() { + backdropService.close(); + } + + /** + * Handles when a search result is clicked + */ + function clickItem() { + closeSearch(); + } + + /** + * Clears the search query + */ + function clearSearch() { + vm.searchQuery = ""; + vm.searchResults = []; + vm.hasResults = false; + focusSearch(); + } + + /** + * Add focus to the search field + */ + function focusSearch() { + vm.searchHasFocus = false; + $timeout(function(){ + vm.searchHasFocus = true; + }); + } + + /** + * Handles all keyboard events + * @param {object} event + */ + function handleKeyUp(event) { + // esc + if(event.keyCode === 27) { + closeSearch(); + } + } + + /** + * Used to proxy a callback + */ + function closeSearch() { + if(vm.onClose) { + vm.onClose(); + } + } + + /** + * Used to search + * @param {string} searchQuery + */ + function search(searchQuery) { + if(searchQuery.length > 0) { + var search = {"term": searchQuery}; + searchService.searchAll(search).then(function(result){ + //result is a dictionary of group Title and it's results + var filtered = {}; + _.each(result, function (value, key) { + if (value.results.length > 0) { + filtered[key] = value; + } + }); + // bind to view model + vm.searchResults = filtered; + // check if search has results + vm.hasResults = Object.keys(vm.searchResults).length > 0; + }); + + } else { + clearSearch(); + } + } + + } + + angular.module('umbraco.directives').component('umbSearch', umbSearch); + +})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js index 7333d033b1..2970c52bbb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js @@ -455,13 +455,28 @@ In the following example you see how to run some custom logic before a step goes function waitForPendingRerequests() { var deferred = $q.defer(); var timer = window.setInterval(function(){ + + var requestsReady = false; + var animationsDone = false; + // check for pending requests both in angular and on the document if($http.pendingRequests.length === 0 && document.readyState === "complete") { + requestsReady = true; + } + + // check for animations. ng-enter and ng-leave are default angular animations. + // Also check for infinite editors animating + if(document.querySelectorAll(".ng-enter, .ng-leave, .umb-editor--animating").length === 0) { + animationsDone = true; + } + + if(requestsReady && animationsDone) { $timeout(function(){ deferred.resolve(); clearInterval(timer); }); } + }, 50); return deferred.promise; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js index e26f622d2e..1d52c4e451 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js @@ -65,6 +65,8 @@ Use this directive to render an umbraco button. The directive can be used to gen @param {string=} icon Set a button icon. @param {string=} size Set a button icon ("xs", "m", "l", "xl"). @param {boolean=} disabled Set to true to disable the button. +@param {string=} addEllipsis Adds an ellipsis character (…) to the button label which means the button will open a dialog or prompt the user for more information. + **/ (function () { @@ -90,14 +92,15 @@ Use this directive to render an umbraco button. The directive can be used to gen icon: "@?", disabled: " 1; } - /** - * The content item(s) are loaded into an array and this will set the active content item based on the current culture (query string). - * If the content item is invariant, then only one item exists in the array. - */ - function setActiveCulture() { - // set the active variant - var activeVariant = null; - _.each($scope.content.variants, function (v) { - if (v.language && v.language.culture === $scope.culture) { - v.active = true; - activeVariant = v; - } - else { - v.active = false; - } - }); - if (!activeVariant) { - // set the first variant to active - $scope.content.variants[0].active = true; - activeVariant = $scope.content.variants[0]; - } - - initVariant(activeVariant); - - //If there are no editors yet then create one with the current content. - //if there's already a main editor then update it with the current content. - if ($scope.editors.length === 0) { - var editor = { - content: activeVariant - }; - $scope.editors.push(editor); - } - else { - //this will mean there is only one - $scope.editors[0].content = activeVariant; - - if ($scope.editors.length > 1) { - //now re-sync any other editor content (i.e. if split view is open) - for (var s = 1; s < $scope.editors.length; s++) { - //get the variant from the scope model - var variant = _.find($scope.content.variants, function (v) { - return v.language.culture === $scope.editors[s].content.language.culture; - }); - $scope.editors[s].content = initVariant(variant); - } - } - - } - } - - function initVariant(variant) { - //The model that is assigned to the editor contains the current content variant along - //with a copy of the contentApps. This is required because each editor renders it's own - //header and content apps section and the content apps contains the view for editing content itself - //and we need to assign a view model to the subView so that it is scoped to the current - //editor so that split views work. - - //copy the apps from the main model if not assigned yet to the variant - if (!variant.apps) { - variant.apps = angular.copy($scope.content.apps); - } - - //if this is a variant has a culture/language than we need to assign the language drop down info - if (variant.language) { - //if the variant list that defines the header drop down isn't assigned to the variant then assign it now - if (!variant.variants) { - variant.variants = _.map($scope.content.variants, - function (v) { - return _.pick(v, "active", "language", "state"); - }); - } - else { - //merge the scope variants on top of the header variants collection (handy when needing to refresh) - angular.extend(variant.variants, - _.map($scope.content.variants, - function (v) { - return _.pick(v, "active", "language", "state"); - })); - } - - //ensure the current culture is set as the active one - for (var i = 0; i < variant.variants.length; i++) { - if (variant.variants[i].language.culture === variant.language.culture) { - variant.variants[i].active = true; - } - else { - variant.variants[i].active = false; - } - } - } - - //then assign the variant to a view model to the content app - var contentApp = _.find(variant.apps, function (a) { - return a.alias === "content"; - }); - contentApp.viewModel = variant; - - return variant; - } - function bindEvents() { //bindEvents can be called more than once and we don't want to have multiple bound events for (var e in evts) { eventsService.unsubscribe(evts[e]); } - evts.push(eventsService.on("editors.content.changePublishDate", function (event, args) { - createButtons(args.node); - })); - - evts.push(eventsService.on("editors.content.changeUnpublishDate", function (event, args) { - createButtons(args.node); - })); - evts.push(eventsService.on("editors.documentType.saved", function (name, args) { // if this content item uses the updated doc type we need to reload the content item if (args && args.documentType && args.documentType.key === content.documentType.key) { @@ -249,9 +124,7 @@ init($scope.content); - if (!infiniteMode) { - syncTreeNode($scope.content, true); - } + syncTreeNode($scope.content, $scope.content.path, true); resetLastListPageNumber($scope.content); @@ -264,7 +137,31 @@ } - function createButtons(content) { + /** + * Create the save/publish/preview buttons for the view + * @param {any} content the content node + * @param {any} app the active content app + */ + function createButtons(content, app) { + + // only create the save/publish/preview buttons if the + // content app is "Conent" + if(app && app.alias !== "umbContent" && app.alias !== "umbInfo") { + $scope.defaultButton = null; + $scope.subButtons = null; + $scope.page.showSaveButton = false; + $scope.page.showPreviewButton = false; + return; + } + + // create the save button + if(_.contains($scope.content.allowedActions, "A")) { + $scope.page.showSaveButton = true; + // add ellipsis to the save button if it opens the variant overlay + $scope.page.saveButtonEllipsis = content.variants && content.variants.length > 1 ? "true" : "false"; + } + + // create the pubish combo button $scope.page.buttonGroupState = "init"; var buttons = contentEditingHelper.configureContentEditorButtons({ create: $scope.page.isNew, @@ -272,13 +169,14 @@ methods: { saveAndPublish: $scope.saveAndPublish, sendToPublish: $scope.sendToPublish, - save: $scope.save, - unPublish: $scope.unPublish + unpublish: $scope.unpublish, + schedulePublish: $scope.schedule } }); $scope.defaultButton = buttons.defaultButton; $scope.subButtons = buttons.subButtons; + $scope.page.showPreviewButton = true; } @@ -300,9 +198,11 @@ } /** Syncs the content item to it's tree node - this occurs on first load and after saving */ - function syncTreeNode(content, initialLoad) { + function syncTreeNode(content, path, initialLoad) { - var path = content.path; + if (infiniteMode || !path) { + return; + } if (!$scope.content.isChildOfListView) { navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { @@ -314,7 +214,7 @@ //it's a child item, just sync the ui node to the parent navigationService.syncTree({ tree: $scope.treeAlias, path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true }); - //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node + //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node // from the server so that we can load in the actions menu. umbRequestHelper.resourcePromise( $http.get(content.treeNodeUrl), @@ -324,11 +224,89 @@ } } + function checkValidility(){ + //Get all controls from the 'contentForm' + var allControls = $scope.contentForm.$getControls(); + + //An array to store items in when we find child form fields (no matter how many deep nested forms) + var childFieldsToMarkAsValid = []; + + //Exclude known formControls 'contentHeaderForm' and 'tabbedContentForm' + //Check property - $name === "contentHeaderForm" + allControls = _.filter(allControls, function(obj){ + return obj.$name !== 'contentHeaderForm' && obj.$name !== 'tabbedContentForm' && obj.hasOwnProperty('$submitted'); + }); + + for (var i = 0; i < allControls.length; i++) { + var nestedForm = allControls[i]; + + //Get Nested Controls of this form in the loop + var nestedFormControls = nestedForm.$getControls(); + + //Need to recurse through controls (could be more nested forms) + childFieldsToMarkAsValid = recurseFormControls(nestedFormControls, childFieldsToMarkAsValid); + } + + return childFieldsToMarkAsValid; + } + + //Controls is the + function recurseFormControls(controls, array){ + + //Loop over the controls + for (var i = 0; i < controls.length; i++) { + var controlItem = controls[i]; + + //Check if the controlItem has a property '' + if(controlItem.hasOwnProperty('$submitted')){ + //This item is a form - so lets get the child controls of it & recurse again + var childFormControls = controlItem.$getControls(); + recurseFormControls(childFormControls, array); + } + else { + //We can assume its a field on a form + if(controlItem.hasOwnProperty('$error')){ + //Set the validlity of the error/s to be valid + //String of keys of error invalid messages + var errorKeys = []; + + for(var key in controlItem.$error){ + errorKeys.push(key); + controlItem.$setValidity(key, true); + } + + //Create a basic obj - storing the control item & the error keys + var obj = { 'control': controlItem, 'errorKeys': errorKeys }; + + //Push the updated control into the array - so we can set them back + array.push(obj); + } + } + } + return array; + } + + function resetNestedFieldValiation(array){ + for (var i = 0; i < array.length; i++) { + var item = array[i]; + //Item is an object containing two props + //'control' (obj) & 'errorKeys' (string array) + var fieldControl = item.control; + var fieldErrorKeys = item.errorKeys; + + for(var i = 0; i < fieldErrorKeys.length; i++) { + fieldControl.$setValidity(fieldErrorKeys[i], false); + } + } + } + // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish function performSave(args) { + - $scope.page.buttonGroupState = "busy"; - + //Used to check validility of nested form - coming from Content Apps mostly + //Set them all to be invalid + var fieldsToRollback = checkValidility(); eventsService.emit("content.saving", { content: $scope.content, action: args.action }); return contentEditingHelper.contentEditorPerformSave({ @@ -338,35 +316,43 @@ action: args.action, showNotifications: args.showNotifications }).then(function (data) { - //success + //success init($scope.content); - - if (!infiniteMode) { - syncTreeNode($scope.content); - } - - $scope.page.buttonGroupState = "success"; + syncTreeNode($scope.content, data.path); eventsService.emit("content.saved", { content: $scope.content, action: args.action }); + resetNestedFieldValiation(fieldsToRollback); + return $q.when(data); }, function (err) { - - setActiveCulture(); - syncTreeNode($scope.content); + syncTreeNode($scope.content, $scope.content.path); //error if (err) { editorState.set($scope.content); } - $scope.page.buttonGroupState = "error"; + resetNestedFieldValiation(fieldsToRollback); return $q.reject(err); }); } + function clearNotifications(content) { + if (content.notifications) { + content.notifications = []; + } + if (content.variants) { + for (var i = 0; i < content.variants.length; i++) { + if (content.variants[i].notifications) { + content.variants[i].notifications = []; + } + } + } + } + function resetLastListPageNumber(content) { // We're using rootScope to store the page number for list views, so if returning to the list // we can restore the page. If we've moved on to edit a piece of content that's not the list or it's children @@ -376,6 +362,24 @@ } } + /** + * Used to clear the dirty state for successfully saved variants when not all variant saving was successful + * @param {any} variants + */ + function clearDirtyState(variants) { + for (var i = 0; i < variants.length; i++) { + var v = variants[i]; + if (v.notifications) { + var isSuccess = _.find(v.notifications, function (n) { + return n.type === 3; //this is a success notification + }); + if (isSuccess) { + v.isDirty = false; + } + } + } + } + if ($scope.page.isNew) { $scope.page.loading = true; @@ -405,91 +409,121 @@ }); } - $scope.unPublish = function () { + $scope.unpublish = function() { + clearNotifications($scope.content); + if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { + var dialog = { + parentScope: $scope, + view: "views/content/overlays/unpublish.html", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabelKey: "content_unpublish", + submit: function (model) { - //if there's any variants than we need to set the language and include the variants to publish - var culture = null; - if ($scope.content.variants.length > 0) { - _.each($scope.content.variants, - function (d) { - //set the culture if this is active - if (d.active === true) { - culture = d.language.culture; - } - }); + model.submitButtonState = "busy"; + + var selectedVariants = _.filter(model.variants, function(variant) { return variant.save; }); + var culturesForUnpublishing = _.map(selectedVariants, function(variant) { return variant.language.culture; }); + + contentResource.unpublish($scope.content.id, culturesForUnpublishing) + .then(function (data) { + formHelper.resetForm({ scope: $scope }); + contentEditingHelper.reBindChangedProperties($scope.content, data); + init($scope.content); + syncTreeNode($scope.content, data.path); + $scope.page.buttonGroupState = "success"; + eventsService.emit("content.unpublished", { content: $scope.content }); + overlayService.close(); + }, function (err) { + $scope.page.buttonGroupState = 'error'; + }); + + + }, + close: function () { + overlayService.close(); + } + }; + overlayService.open(dialog); } - - if (formHelper.submitForm({ scope: $scope, skipValidation: true })) { - - $scope.page.buttonGroupState = "busy"; - - eventsService.emit("content.unpublishing", { content: $scope.content }); - - contentResource.unPublish($scope.content.id, culture) - .then(function (data) { - - formHelper.resetForm({ scope: $scope }); - - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - savedContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) - }); - - init($scope.content); - - if (!infiniteMode) { - syncTreeNode($scope.content); - } - - $scope.page.buttonGroupState = "success"; - - eventsService.emit("content.unpublished", { content: $scope.content }); - - }, function (err) { - $scope.page.buttonGroupState = 'error'; - }); - } - }; - + $scope.sendToPublish = function () { - return performSave({ saveMethod: contentResource.sendToPublish, action: "sendToPublish" }); - }; - - $scope.saveAndPublish = function () { - - // TODO: Add "..." to publish button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant + clearNotifications($scope.content); if (showSaveOrPublishDialog()) { //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publish" })) { var dialog = { + parentScope: $scope, + view: "views/content/overlays/sendtopublish.html", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabel: "Send for approval", + submit: function (model) { + model.submitButtonState = "busy"; + clearNotifications($scope.content); + //we need to return this promise so that the dialog can handle the result and wire up the validation response + console.log("saving need to happen here"); + }, + close: function () { + overlayService.close(); + } + }; + + overlayService.open(dialog); + } + } + else { + $scope.page.buttonGroupState = "busy"; + return performSave({ + saveMethod: contentResource.sendToPublish, + action: "sendToPublish" + }).then(function(){ + $scope.page.buttonGroupState = "success"; + }, function () { + $scope.page.buttonGroupState = "error"; + });; + } + }; + + $scope.saveAndPublish = function () { + clearNotifications($scope.content); + if (showSaveOrPublishDialog()) { + //before we launch the dialog we want to execute all client side validations first + if (formHelper.submitForm({ scope: $scope, action: "publish" })) { + + var dialog = { + parentScope: $scope, view: "views/content/overlays/publish.html", variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabel: "Publish", submit: function (model) { model.submitButtonState = "busy"; - + clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response return performSave({ saveMethod: contentResource.publish, action: "publish", showNotifications: false }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); overlayService.close(); return $q.when(data); }, function (err) { + clearDirtyState($scope.content.variants); model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties dialog.variants = $scope.content.variants; - - return $q.reject(err); + //don't reject, we've handled the error + return $q.when(err); }); }, - close: function (oldModel) { + close: function () { overlayService.close(); } }; @@ -500,41 +534,53 @@ else { //ensure the publish flag is set $scope.content.variants[0].publish = true; - return performSave({ saveMethod: contentResource.publish, action: "publish" }); + $scope.page.buttonGroupState = "busy"; + return performSave({ + saveMethod: contentResource.publish, + action: "publish" + }).then(function(){ + $scope.page.buttonGroupState = "success"; + }, function () { + $scope.page.buttonGroupState = "error"; + });; } }; $scope.save = function () { - + clearNotifications($scope.content); // TODO: Add "..." to save button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant if (showSaveOrPublishDialog()) { //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "save" })) { var dialog = { + parentScope: $scope, view: "views/content/overlays/save.html", variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabel: "Save", submit: function (model) { model.submitButtonState = "busy"; - + clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response return performSave({ saveMethod: $scope.saveMethod(), action: "save", showNotifications: false }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); overlayService.close(); return $q.when(data); - }, - function (err) { - model.submitButtonState = "error"; - //re-map the dialog model since we've re-bound the properties - dialog.variants = $scope.content.variants; - - return $q.reject(err); - }); + }, function (err) { + clearDirtyState($scope.content.variants); + model.submitButtonState = "error"; + //re-map the dialog model since we've re-bound the properties + dialog.variants = $scope.content.variants; + //don't reject, we've handled the error + return $q.when(err); + }); }, close: function (oldModel) { overlayService.close(); @@ -545,23 +591,59 @@ } } else { - return performSave({ saveMethod: $scope.saveMethod(), action: "save" }); + $scope.page.saveButtonState = "busy"; + return performSave({ + saveMethod: $scope.saveMethod(), + action: "save" + }).then(function(){ + $scope.page.saveButtonState = "success"; + }, function () { + $scope.page.saveButtonState = "error"; + }); } }; + $scope.schedule = function() { + clearNotifications($scope.content); + //before we launch the dialog we want to execute all client side validations first + if (formHelper.submitForm({ scope: $scope, action: "schedule" })) { + + var dialog = { + parentScope: $scope, + view: "views/content/overlays/schedule.html", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabel: "Schedule", + submit: function (model) { + model.submitButtonState = "busy"; + clearNotifications($scope.content); + model.submitButtonState = "success"; + }, + close: function () { + overlayService.close(); + } + }; + overlayService.open(dialog); + } + }; + $scope.preview = function (content) { if (!$scope.busy) { - // Chromes popup blocker will kick in if a window is opened + // Chromes popup blocker will kick in if a window is opened // without the initial scoped request. This trick will fix that. - // - var previewWindow = $window.open('preview/?init=true&id=' + content.id, 'umbpreview'); + // + var previewWindow = $window.open('preview/?init=true', 'umbpreview'); // Build the correct path so both /#/ and #/ work. - var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + content.id; + var query = 'id=' + content.id; + if ($scope.culture) { + query += "&culture=" + $scope.culture; + } + var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?' + query; //The user cannot save if they don't have access to do that, in which case we just want to preview //and that's it otherwise they'll get an unauthorized access message @@ -569,17 +651,27 @@ previewWindow.location.href = redirect; } else { - $scope.save().then(function (data) { + var selectedVariant; + if (!$scope.culture) { + selectedVariant = $scope.content.variants[0]; + } + else { + selectedVariant = _.find($scope.content.variants, function (v) { + return v.language.culture === $scope.culture; + }); + } + + //ensure the save flag is set + selectedVariant.save = true; + performSave({ saveMethod: contentResource.publish, action: "save" }).then(function (data) { previewWindow.location.href = redirect; + }, function (err) { + //validation issues .... }); } } }; - $scope.backToListView = function () { - $location.path($scope.page.listViewPath); - }; - $scope.restore = function (content) { $scope.page.buttonRestore = "busy"; @@ -650,6 +742,14 @@ }); }; + /** + * Call back when a content app changes + * @param {any} app + */ + $scope.appChanged = function(app) { + createButtons($scope.content, app); + }; + function moveNode(node, target) { contentResource.move({ "parentId": target.id, "id": node.id }) @@ -685,12 +785,6 @@ } }; - $scope.$watch('culture', function (newVal, oldVal) { - if (newVal !== oldVal) { - setActiveCulture(); - } - }); - //ensure to unregister from all events! $scope.$on('$destroy', function () { for (var e in evts) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index b0ecb65eea..b2e64983d6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -1,7 +1,7 @@ (function () { 'use strict'; - function ContentNodeInfoDirective($timeout, $location, logResource, eventsService, userService, localizationService, dateHelper, editorService) { + function ContentNodeInfoDirective($timeout, $location, logResource, eventsService, userService, localizationService, dateHelper, editorService, redirectUrlsResource) { function link(scope, element, attrs, ctrl) { @@ -13,7 +13,7 @@ scope.disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates; scope.allowChangeDocumentType = false; scope.allowChangeTemplate = false; - + function onInit() { userService.getCurrentUser().then(function(user){ @@ -46,19 +46,6 @@ }); - scope.datePickerConfig = { - pickDate: true, - pickTime: true, - useSeconds: false, - format: "YYYY-MM-DD HH:mm", - icons: { - time: "icon-time", - date: "icon-calendar", - up: "icon-chevron-up", - down: "icon-chevron-down" - } - }; - scope.auditTrailOptions = { "id": scope.node.id }; @@ -69,12 +56,13 @@ // get document type details scope.documentType = scope.node.documentType; - // make sure dates are formatted to the user's locale - formatDatesToLocal(); - - // Declare a fallback URL for the directive - scope.previewOpenUrl = '#/settings/documenttypes/edit/' + scope.documentType.id; + //default setting for redirect url management + scope.urlTrackerDisabled = false; + // Declare a fallback URL for the directive + if (scope.documentType !== null) { + scope.previewOpenUrl = '#/settings/documenttypes/edit/' + scope.documentType.id; + } } scope.auditTrailPageChange = function (pageNumber) { @@ -113,22 +101,6 @@ scope.node.template = templateAlias; }; - scope.datePickerChange = function (event, type) { - if (type === 'publish') { - setPublishDate(event.date.format("YYYY-MM-DD HH:mm")); - } else if (type === 'unpublish') { - setUnpublishDate(event.date.format("YYYY-MM-DD HH:mm")); - } - }; - - scope.clearPublishDate = function () { - clearPublishDate(); - }; - - scope.clearUnpublishDate = function () { - clearUnpublishDate(); - }; - function loadAuditTrail() { scope.loadingAuditTrail = true; @@ -138,11 +110,11 @@ // get current backoffice user and format dates userService.getCurrentUser().then(function (currentUser) { - angular.forEach(data.items, function(item) { + angular.forEach(data.items, function (item) { item.timestampFormatted = dateHelper.getLocalDate(item.timestamp, currentUser.locale, 'LLL'); }); }); - + scope.auditTrail = data.items; scope.auditTrailOptions.pageNumber = data.pageNumber; scope.auditTrailOptions.pageSize = data.pageSize; @@ -150,19 +122,39 @@ scope.auditTrailOptions.totalPages = data.totalPages; setAuditTrailLogTypeColor(scope.auditTrail); - + scope.loadingAuditTrail = false; }); } + function loadRedirectUrls() { + scope.loadingRedirectUrls = true; + //check if Redirect Url Management is enabled + redirectUrlsResource.getEnableState().then(function (response) { + scope.urlTrackerDisabled = response.enabled !== true; + if (scope.urlTrackerDisabled === false) { + + redirectUrlsResource.getRedirectsForContentItem(scope.node.udi) + .then(function (data) { + scope.redirectUrls = data.searchResults; + scope.hasRedirects = (typeof data.searchResults !== 'undefined' && data.searchResults.length > 0); + scope.loadingRedirectUrls = false; + }); + } + else { + scope.loadingRedirectUrls = false; + } + }); + } function setAuditTrailLogTypeColor(auditTrail) { angular.forEach(auditTrail, function (item) { + switch (item.logType) { case "Publish": item.logTypeColor = "success"; break; - case "UnPublish": + case "Unpublish": case "Delete": item.logTypeColor = "danger"; break; @@ -219,103 +211,13 @@ } } - function setPublishDate(date) { - - if (!date) { - return; - } - - //The date being passed in here is the user's local date/time that they have selected - //we need to convert this date back to the server date on the model. - - var serverTime = dateHelper.convertToServerStringTime(moment(date), Umbraco.Sys.ServerVariables.application.serverTimeOffset); - - // update publish value - scope.node.releaseDate = serverTime; - - // make sure dates are formatted to the user's locale - formatDatesToLocal(); - - // emit event - var args = { node: scope.node, date: date }; - eventsService.emit("editors.content.changePublishDate", args); - - } - - function clearPublishDate() { - - // update publish value - scope.node.releaseDate = null; - - // emit event - var args = { node: scope.node, date: null }; - eventsService.emit("editors.content.changePublishDate", args); - - } - - function setUnpublishDate(date) { - - if (!date) { - return; - } - - //The date being passed in here is the user's local date/time that they have selected - //we need to convert this date back to the server date on the model. - - var serverTime = dateHelper.convertToServerStringTime(moment(date), Umbraco.Sys.ServerVariables.application.serverTimeOffset); - - // update publish value - scope.node.removeDate = serverTime; - - // make sure dates are formatted to the user's locale - formatDatesToLocal(); - - // emit event - var args = { node: scope.node, date: date }; - eventsService.emit("editors.content.changeUnpublishDate", args); - - } - - function clearUnpublishDate() { - - // update publish value - scope.node.removeDate = null; - - // emit event - var args = { node: scope.node, date: null }; - eventsService.emit("editors.content.changeUnpublishDate", args); - - } - - function ucfirst(string) { - return string.charAt(0).toUpperCase() + string.slice(1); - } - - function formatDatesToLocal() { - // get current backoffice user and format dates - userService.getCurrentUser().then(function (currentUser) { - scope.node.createDateFormatted = dateHelper.getLocalDate(scope.node.createDate, currentUser.locale, 'LLL'); - - scope.node.releaseDateYear = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'YYYY')) : null; - scope.node.releaseDateMonth = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'MMMM')) : null; - scope.node.releaseDateDayNumber = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'DD')) : null; - scope.node.releaseDateDay = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'dddd')) : null; - scope.node.releaseDateTime = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'HH:mm')) : null; - - scope.node.removeDateYear = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'YYYY')) : null; - scope.node.removeDateMonth = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'MMMM')) : null; - scope.node.removeDateDayNumber = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'DD')) : null; - scope.node.removeDateDay = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'dddd')) : null; - scope.node.removeDateTime = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'HH:mm')) : null; - }); - } - - // load audit trail when on the info tab + // load audit trail and redirects when on the info tab evts.push(eventsService.on("app.tabChange", function (event, args) { - $timeout(function(){ - if (args.alias === "info") { + $timeout(function () { + if (args.alias === "umbInfo") { isInfoTab = true; loadAuditTrail(); + loadRedirectUrls(); } else { isInfoTab = false; } @@ -323,14 +225,14 @@ })); // watch for content state updates - scope.$watch('node.updateDate', function(newValue, oldValue){ + scope.$watch('node.updateDate', function (newValue, oldValue) { + + if (!newValue) { return; } + if (newValue === oldValue) { return; } - if(!newValue) { return; } - if(newValue === oldValue) { return; } - if(isInfoTab) { loadAuditTrail(); - formatDatesToLocal(); + loadRedirectUrls(); setNodePublishStatus(scope.node); } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js index cdc23ee985..8545854992 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js @@ -2,133 +2,104 @@ 'use strict'; /** - * A directive to encapsulate each variant editor which includes the name header and all content apps for a given variant - * @param {any} $timeout - * @param {any} $location + * A component to encapsulate each variant editor which includes the name header and all content apps for a given variant */ - function variantContentDirective($timeout, $location) { + var umbVariantContent = { + templateUrl: 'views/components/content/umb-variant-content.html', + bindings: { + content: "<", + page: "<", + editor: "<", + editorIndex: "<", + editorCount: "<", + openVariants: "<", + onCloseSplitView: "&", + onSelectVariant: "&", + onOpenSplitView: "&", + onSelectApp: "&" + }, + controllerAs: 'vm', + controller: umbVariantContentController + }; + + function umbVariantContentController($scope, $element, $location) { - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/content/umb-variant-content.html', - link: function (scope) { + var unsubscribe = []; - /** - * Adds a new editor to the editors array to show content in a split view - * @param {any} selectedVariant - */ - scope.openInSplitView = function (selectedVariant) { + var vm = this; - var selectedCulture = selectedVariant.language.culture; + vm.$onInit = onInit; + vm.$postLink = postLink; + vm.$onDestroy = onDestroy; - //only the content app can be selected since no other apps are shown, and because we copy all of these apps - //to the "editors" we need to update this across all editors - for (var e = 0; e < scope.editors.length; e++) { - var editor = scope.editors[e]; - for (var i = 0; i < editor.content.apps.length; i++) { - var app = editor.content.apps[i]; - if (app.alias === "content") { - app.active = true; - } - else { - app.active = false; - } - } + vm.selectVariant = selectVariant; + vm.openSplitView = openSplitView; + vm.selectApp = selectApp; + + function onInit() { + // disable the name field if the active content app is not "Content" + vm.nameDisabled = false; + angular.forEach(vm.editor.content.apps, function(app){ + if(app.active && app.alias !== "umbContent" && app.alias !== "umbInfo") { + vm.nameDisabled = true; + } + }); + } + + /** Called when the component has linked all elements, this is when the form controller is available */ + function postLink() { + //set the content to dirty if the header changes + unsubscribe.push($scope.$watch("contentHeaderForm.$dirty", + function(newValue, oldValue) { + if (newValue === true) { + vm.editor.content.isDirty = true; } - - //Find the whole variant model based on the culture that was chosen - var variant = _.find(scope.content.variants, function (v) { - return v.language.culture === selectedCulture; - }); - - var editor = { - content: scope.initVariant({ variant: variant}) - }; - scope.editors.push(editor); - - //TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular - editor.collapsed = true; - editor.loading = true; - $timeout(function () { - editor.collapsed = false; - editor.loading = false; - scope.onSplitViewChanged(); - }, 100); - }; - - /** - * Changes the currently selected variant - * @param {any} variantDropDownItem - */ - scope.selectVariant = function (variantDropDownItem) { - - var editorIndex = _.findIndex(scope.editors, function (e) { - return e === scope.editor; - }); - - //if the editor index is zero, then update the query string to track the lang selection, otherwise if it's part - //of a 2nd split view editor then update the model directly. - if (editorIndex === 0) { - //if we've made it this far, then update the query string - $location.search("cculture", variantDropDownItem.language.culture); - } - else { - //set all variant drop down items as inactive for this editor and then set the selected on as active - for (var i = 0; i < scope.editor.content.variants.length; i++) { - scope.editor.content.variants[i].active = false; - } - variantDropDownItem.active = true; - - //get the variant content model and initialize the editor with that - var variant = _.find(scope.content.variants, function (v) { - return v.language.culture === variantDropDownItem.language.culture; - }); - scope.editor.content = scope.initVariant({ variant: variant }); - } - }; - - /** Closes the split view */ - scope.closeSplitView = function () { - //TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular - scope.editor.loading = true; - scope.editor.collapsed = true; - $timeout(function () { - var index = _.findIndex(scope.editors, function(e) { - return e === scope.editor; - }); - scope.editors.splice(index, 1); - scope.onSplitViewChanged(); - }, 400); - }; - - //set the content to dirty if the header changes - scope.$watch("contentHeaderForm.$dirty", - function (newValue, oldValue) { - if (newValue === true) { - scope.editor.content.isDirty = true; - } - }); - - }, - scope: { - //TODO: This should be turned into a proper component - - page: "=", - content: "=", - editor: "=", - editors: "=", - //TODO: I don't like having this callback defined and would like to make this directive a bit less - // coupled but right now don't have time - initVariant: "&", - onSplitViewChanged: "&" + })); + } + + function onDestroy() { + for (var i = 0; i < unsubscribe.length; i++) { + unsubscribe[i](); } - }; + } - return directive; + /** + * Used to proxy a callback + * @param {any} variant + */ + function selectVariant(variant) { + if (vm.onSelectVariant) { + vm.onSelectVariant({ "variant": variant }); + } + } + /** + * Used to proxy a callback + * @param {any} item + */ + function selectApp(item) { + // disable the name field if the active content app is not "Content" or "Info" + vm.nameDisabled = false; + if(item && item.alias !== "umbContent" && item.alias !== "umbInfo") { + vm.nameDisabled = true; + } + // call the callback if any is registered + if(vm.onSelectApp) { + vm.onSelectApp({"app": item}); + } + } + + /** + * Used to proxy a callback + * @param {any} variant + */ + function openSplitView(variant) { + if (vm.onOpenSplitView) { + vm.onOpenSplitView({ "variant": variant }); + } + } } - angular.module('umbraco.directives').directive('umbVariantContent', variantContentDirective); + angular.module('umbraco.directives').component('umbVariantContent', umbVariantContent); })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js new file mode 100644 index 0000000000..a3a212a603 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js @@ -0,0 +1,324 @@ +(function () { + 'use strict'; + + /** + * A component for split view content editing + */ + var umbVariantContentEditors = { + templateUrl: 'views/components/content/umb-variant-content-editors.html', + bindings: { + page: "<", + content: "<", //TODO: Not sure if this should be = since we are changing the 'active' property of a variant + culture: "<", + onSelectApp: "&?" + }, + controllerAs: 'vm', + controller: umbVariantContentEditorsController + }; + + function umbVariantContentEditorsController($scope, $location, $timeout) { + + var prevContentDateUpdated = null; + + var vm = this; + var activeAppAlias = null; + + vm.$onInit = onInit; + vm.$onChanges = onChanges; + vm.$doCheck = doCheck; + vm.$postLink = postLink; + + vm.openSplitView = openSplitView; + vm.closeSplitView = closeSplitView; + vm.selectVariant = selectVariant; + vm.selectApp = selectApp; + + //Used to track how many content views there are (for split view there will be 2, it could support more in theory) + vm.editors = []; + //Used to track the open variants across the split views + vm.openVariants = []; + + /** Called when the component initializes */ + function onInit() { + prevContentDateUpdated = angular.copy(vm.content.updateDate); + setActiveCulture(); + } + + /** Called when the component has linked all elements, this is when the form controller is available */ + function postLink() { + + } + + /** + * Watch for model changes + * @param {any} changes + */ + function onChanges(changes) { + + if (changes.culture && !changes.culture.isFirstChange() && changes.culture.currentValue !== changes.culture.previousValue) { + setActiveCulture(); + } + } + + /** Allows us to deep watch whatever we want - executes on every digest cycle */ + function doCheck() { + if (!angular.equals(vm.content.updateDate, prevContentDateUpdated)) { + setActiveCulture(); + prevContentDateUpdated = angular.copy(vm.content.updateDate); + } + } + + /** This is called when the split view changes based on the umb-variant-content */ + function splitViewChanged() { + //send an event downwards + $scope.$broadcast("editors.content.splitViewChanged", { editors: vm.editors }); + } + + /** + * Set the active variant based on the current culture (query string) + */ + function setActiveCulture() { + // set the active variant + var activeVariant = null; + _.each(vm.content.variants, function (v) { + if (v.language && v.language.culture === vm.culture) { + v.active = true; + activeVariant = v; + } + else { + v.active = false; + } + }); + if (!activeVariant) { + // Set the first variant to active if we can't find it. + // If the content item is invariant, then only one item exists in the array. + vm.content.variants[0].active = true; + activeVariant = vm.content.variants[0]; + } + + insertVariantEditor(0, initVariant(activeVariant, 0)); + + if (vm.editors.length > 1) { + //now re-sync any other editor content (i.e. if split view is open) + for (var s = 1; s < vm.editors.length; s++) { + //get the variant from the scope model + var variant = _.find(vm.content.variants, function (v) { + return v.language.culture === vm.editors[s].content.language.culture; + }); + vm.editors[s].content = initVariant(variant, s); + } + } + + } + + /** + * Updates the editors collection for a given index for the specified variant + * @param {any} index + * @param {any} variant + */ + function insertVariantEditor(index, variant) { + + var variantCulture = variant.language ? variant.language.culture : "invariant"; + + //check if the culture at the index is the same, if it's null an editor will be added + var currentCulture = vm.editors.length === 0 || vm.editors.length <= index ? null : vm.editors[index].culture; + + if (currentCulture !== variantCulture) { + //Not the current culture which means we need to modify the array. + //NOTE: It is not good enough to just replace the `content` object at a given index in the array + // since that would mean that directives are not re-initialized. + vm.editors.splice(index, 1, { + content: variant, + //used for "track-by" ng-repeat + culture: variantCulture + }); + } + else { + //replace the editor for the same culture + vm.editors[index].content = variant; + } + } + + function initVariant(variant, editorIndex) { + //The model that is assigned to the editor contains the current content variant along + //with a copy of the contentApps. This is required because each editor renders it's own + //header and content apps section and the content apps contains the view for editing content itself + //and we need to assign a view model to the subView so that it is scoped to the current + //editor so that split views work. + + //copy the apps from the main model if not assigned yet to the variant + if (!variant.apps) { + variant.apps = angular.copy(vm.content.apps); + } + + //if this is a variant has a culture/language than we need to assign the language drop down info + if (variant.language) { + //if the variant list that defines the header drop down isn't assigned to the variant then assign it now + if (!variant.variants) { + variant.variants = _.map(vm.content.variants, + function (v) { + return _.pick(v, "active", "language", "state"); + }); + } + else { + //merge the scope variants on top of the header variants collection (handy when needing to refresh) + angular.extend(variant.variants, + _.map(vm.content.variants, + function (v) { + return _.pick(v, "active", "language", "state"); + })); + } + + //ensure the current culture is set as the active one + for (var i = 0; i < variant.variants.length; i++) { + if (variant.variants[i].language.culture === variant.language.culture) { + variant.variants[i].active = true; + } + else { + variant.variants[i].active = false; + } + } + + // keep track of the open variants across the different split views + // push the first variant then update the variant index based on the editor index + if(vm.openVariants && vm.openVariants.length === 0) { + vm.openVariants.push(variant.language.culture); + } else { + vm.openVariants[editorIndex] = variant.language.culture; + } + + } + + //then assign the variant to a view model to the content app + var contentApp = _.find(variant.apps, function (a) { + return a.alias === "umbContent"; + }); + + contentApp.viewModel = _.omit(variant, 'apps'); + + // make sure the same app it set to active in the new variant + if(activeAppAlias) { + angular.forEach(variant.apps, function(app) { + app.active = false; + if(app.alias === activeAppAlias) { + app.active = true; + } + }); + } + + return variant; + } + /** + * Adds a new editor to the editors array to show content in a split view + * @param {any} selectedVariant + */ + function openSplitView(selectedVariant) { + + var selectedCulture = selectedVariant.language.culture; + + //only the content app can be selected since no other apps are shown, and because we copy all of these apps + //to the "editors" we need to update this across all editors + for (var e = 0; e < vm.editors.length; e++) { + var editor = vm.editors[e]; + for (var i = 0; i < editor.content.apps.length; i++) { + var app = editor.content.apps[i]; + if (app.alias === "umbContent") { + app.active = true; + } + else { + app.active = false; + } + } + } + + //Find the whole variant model based on the culture that was chosen + var variant = _.find(vm.content.variants, function (v) { + return v.language.culture === selectedCulture; + }); + + insertVariantEditor(vm.editors.length, initVariant(variant, vm.editors.length)); + + //TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular + editor.collapsed = true; + editor.loading = true; + $timeout(function () { + editor.collapsed = false; + editor.loading = false; + splitViewChanged(); + }, 100); + } + + /** Closes the split view */ + function closeSplitView(editorIndex) { + //TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular + var editor = vm.editors[editorIndex]; + editor.loading = true; + editor.collapsed = true; + $timeout(function () { + vm.editors.splice(editorIndex, 1); + //remove variant from open variants + vm.openVariants.splice(editorIndex, 1); + splitViewChanged(); + }, 400); + } + + /** + * Changes the currently selected variant + * @param {any} variant This is the model of the variant/language drop down item in the editor header + * @param {any} editorIndex The index of the editor being changed + */ + function selectVariant(variant, editorIndex) { + + // prevent variants already open in a split view to be opened + if(vm.openVariants.indexOf(variant.language.culture) !== -1) { + return; + } + + //if the editor index is zero, then update the query string to track the lang selection, otherwise if it's part + //of a 2nd split view editor then update the model directly. + if (editorIndex === 0) { + //If we've made it this far, then update the query string. + //The editor will respond to this query string changing. + $location.search("cculture", variant.language.culture); + } + else { + + //Update the 'active' variant for this editor + var editor = vm.editors[editorIndex]; + //set all variant drop down items as inactive for this editor and then set the selected one as active + for (var i = 0; i < editor.content.variants.length; i++) { + editor.content.variants[i].active = false; + } + variant.active = true; + + //get the variant content model and initialize the editor with that + var contentVariant = _.find(vm.content.variants, + function (v) { + return v.language.culture === variant.language.culture; + }); + editor.content = initVariant(contentVariant, editorIndex); + + //update the editors collection + insertVariantEditor(editorIndex, contentVariant); + + } + } + + /** + * Stores the active app in a variable so we can remember it when changing language + * @param {any} app This is the model of the selected app + */ + function selectApp(app) { + if(app && app.alias) { + activeAppAlias = app.alias; + } + if(vm.onSelectApp) { + vm.onSelectApp({"app": app}); + } + } + + } + + angular.module('umbraco.directives').component('umbVariantContentEditors', umbVariantContentEditors); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js new file mode 100644 index 0000000000..0d78aab0eb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js @@ -0,0 +1,124 @@ +(function () { + 'use strict'; + + function EditorContentHeader($location, $routeParams) { + + function link(scope, el, attr, ctrl) { + + if (!scope.serverValidationNameField) { + scope.serverValidationNameField = "Name"; + } + if (!scope.serverValidationAliasField) { + scope.serverValidationAliasField = "Alias"; + } + + scope.vm = {}; + scope.vm.dropdownOpen = false; + scope.vm.currentVariant = ""; + + function onInit() { + setCurrentVariant(); + } + + function setCurrentVariant() { + angular.forEach(scope.variants, function (variant) { + if (variant.active) { + scope.vm.currentVariant = variant; + } + }); + } + + scope.goBack = function () { + $location.path('/' + $routeParams.section + '/' + $routeParams.tree + '/' + $routeParams.method + '/' + scope.menu.currentNode.parentId); + }; + + scope.selectVariant = function (event, variant) { + + if (scope.onSelectVariant) { + scope.vm.dropdownOpen = false; + scope.onSelectVariant({ "variant": variant }); + } + }; + + scope.selectNavigationItem = function(item) { + if(scope.onSelectNavigationItem) { + scope.onSelectNavigationItem({"item": item}); + } + } + + scope.closeSplitView = function () { + if (scope.onCloseSplitView) { + scope.onCloseSplitView(); + } + }; + + scope.openInSplitView = function (event, variant) { + if (scope.onOpenInSplitView) { + scope.vm.dropdownOpen = false; + scope.onOpenInSplitView({ "variant": variant }); + } + }; + + /** + * keep track of open variants - this is used to prevent the same variant to be open in more than one split view + * @param {any} culture + */ + scope.variantIsOpen = function(culture) { + if(scope.openVariants.indexOf(culture) !== -1) { + return true; + } + } + + onInit(); + + //watch for the active culture changing, if it changes, update the current variant + if (scope.variants) { + scope.$watch(function () { + for (var i = 0; i < scope.variants.length; i++) { + var v = scope.variants[i]; + if (v.active) { + return v.language.culture; + } + } + return scope.vm.currentVariant.language.culture; //should never get here + }, function (newValue, oldValue) { + if (newValue !== scope.vm.currentVariant.language.culture) { + setCurrentVariant(); + } + }); + } + } + + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-content-header.html', + scope: { + name: "=", + nameDisabled: " 0 ){ return; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/hexbackgroundcolor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/hexbackgroundcolor.directive.js index 5a425b7c90..eb64439e0b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/hexbackgroundcolor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/hexbackgroundcolor.directive.js @@ -11,32 +11,36 @@ function hexBgColor() { restrict: "A", link: function (scope, element, attr, formCtrl) { - var origColor = null; - if (attr.hexBgOrig) { - //set the orig based on the attribute if there is one - origColor = attr.hexBgOrig; - } - - attr.$observe("hexBgColor", function (newVal) { - if (newVal) { - if (!origColor) { - //get the orig color before changing it - origColor = element.css("border-color"); - } - //validate it - test with and without the leading hash. - if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) { - element.css("background-color", "#" + newVal); - return; - } - if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) { - element.css("background-color", newVal); - return; - } - } - element.css("background-color", origColor); - }); + // Only add inline hex background color if defined and not "true". + if (attr.hexBgInline === undefined || (attr.hexBgInline !== undefined && attr.hexBgInline === "true")) { + var origColor = null; + if (attr.hexBgOrig) { + // Set the orig based on the attribute if there is one. + origColor = attr.hexBgOrig; + } + + attr.$observe("hexBgColor", function (newVal) { + if (newVal) { + if (!origColor) { + // Get the orig color before changing it. + origColor = element.css("border-color"); + } + // Validate it - test with and without the leading hash. + if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) { + element.css("background-color", "#" + newVal); + return; + } + if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) { + element.css("background-color", newVal); + return; + } + } + + element.css("background-color", origColor); + }); + } } }; } -angular.module('umbraco.directives').directive("hexBgColor", hexBgColor); \ No newline at end of file +angular.module('umbraco.directives').directive("hexBgColor", hexBgColor); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index e18137085b..9bdf94d245 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -44,11 +44,11 @@ angular.module("umbraco.directives") var stylesheets = []; var styleFormats = []; - var await = []; + var requests = []; //queue file loading if (typeof (tinymce) === "undefined") { - await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", scope)); + requests.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", scope)); } @@ -61,7 +61,7 @@ angular.module("umbraco.directives") angular.forEach(scope.configuration.stylesheets, function(stylesheet, key){ stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + stylesheet + ".css"); - await.push(stylesheetResource.getRulesByName(stylesheet).then(function (rules) { + requests.push(stylesheetResource.getRulesByName(stylesheet).then(function (rules) { angular.forEach(rules, function (rule) { var r = {}; var split = ""; @@ -97,7 +97,7 @@ angular.module("umbraco.directives") //stores a reference to the editor var tinyMceEditor = null; - $q.all(await).then(function () { + $q.all(requests).then(function () { var uniqueId = scope.uniqueId; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js index 4f783ac9a9..d56a4d2439 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js @@ -41,7 +41,6 @@ }; function setFocalPoint (event) { - $scope.$emit("imageFocalPointStart"); var offsetX = event.offsetX - 10; @@ -50,7 +49,6 @@ calculateGravity(offsetX, offsetY); lazyEndEvent(); - }; /** Initializes the component */ @@ -148,12 +146,26 @@ /** Sets the width/height/left/top dimentions based on the image size and the "center" value */ function setDimensions() { - if (htmlImage && vm.center) { + + if (vm.src.endsWith(".svg")) { + // svg files don't automatically get a size by + // loading them set a default size for now + vm.dimensions.width = 200; + vm.dimensions.height = 200; + vm.dimensions.left = vm.center.left * vm.dimensions.width - 10; + vm.dimensions.top = vm.center.top * vm.dimensions.height - 10; + // can't crop an svg file, don't show the focal point + if (htmlOverlay) { + htmlOverlay.remove(); + } + } + else if (htmlImage && vm.center) { vm.dimensions.width = htmlImage.width(); vm.dimensions.height = htmlImage.height(); vm.dimensions.left = vm.center.left * vm.dimensions.width - 10; vm.dimensions.top = vm.center.top * vm.dimensions.height - 10; } + return vm.dimensions.width; }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js index 1f2ac9e4b7..ce816261d0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js @@ -22,8 +22,10 @@ // get document type details scope.mediaType = scope.node.contentType; - // get node url - scope.nodeUrl = scope.node.mediaLink; + + // set the media link initially + setMediaLink(); + // make sure dates are formatted to the user's locale formatDatesToLocal(); } @@ -36,6 +38,10 @@ }); } + function setMediaLink(){ + scope.nodeUrl = scope.node.mediaLink; + } + scope.openMediaType = function (mediaType) { var editor = { id: mediaType.id, @@ -48,11 +54,16 @@ }; editorService.mediaTypeEditor(editor); }; - + // watch for content updates - reload content when node is saved, published etc. scope.$watch('node.updateDate', function(newValue, oldValue){ if(!newValue) { return; } if(newValue === oldValue) { return; } + + // Update the media link + setMediaLink(); + + // Update the create and update dates formatDatesToLocal(); }); @@ -64,7 +75,6 @@ }); onInit(); - } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js index d339b4c0f3..07cfbb9848 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js @@ -412,7 +412,7 @@ Opens an overlay to show a custom YSOD.
(function() { 'use strict'; - function OverlayDirective($timeout, formHelper, overlayHelper, localizationService, $q) { + function OverlayDirective($timeout, formHelper, overlayHelper, localizationService, $q, $templateCache, $http, $compile) { function link(scope, el, attr, ctrl) { @@ -424,7 +424,8 @@ Opens an overlay to show a custom YSOD.
var numberOfOverlays = 0; var isRegistered = false; - var modelCopy = {}; + var modelCopy = {}; + var unsubscribe = []; function activate() { @@ -459,6 +460,21 @@ Opens an overlay to show a custom YSOD.
scope.view = "views/common/overlays/" + viewAlias + "/" + viewAlias + ".html"; } + //if a custom parent scope is defined then we need to manually compile the view + if (scope.parentScope) { + var element = el.find(".scoped-view"); + $http.get(scope.view, { cache: $templateCache }) + .then(function (response) { + var templateScope = scope.parentScope.$new(); + unsubscribe.push(function() { + templateScope.$destroy(); + }); + templateScope.model = scope.model; + element.html(response.data); + element.show(); + $compile(element.contents())(templateScope); + }); + } } } @@ -553,7 +569,7 @@ Opens an overlay to show a custom YSOD.
var newObject = {}; for (var key in object) { - if (key !== "event") { + if (key !== "event" && key !== "parentScope") { newObject[key] = angular.copy(object[key]); } } @@ -652,14 +668,14 @@ Opens an overlay to show a custom YSOD.
$q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then( function() { formHelper.resetForm({ scope: scope }); - }, angular.noop); + }); } else { unregisterOverlay(); //wrap in a when since we don't know if this is a promise or not $q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then( function() { formHelper.resetForm({ scope: scope }); - }, angular.noop); + }); } } @@ -684,8 +700,17 @@ Opens an overlay to show a custom YSOD.
}; - scope.$on('$destroy', function(){ - unregisterOverlay(); + scope.outSideClick = function() { + if(!scope.model.disableBackdropClick) { + scope.closeOverLay(); + } + }; + + unsubscribe.push(unregisterOverlay); + scope.$on('$destroy', function () { + for (var i = 0; i < unsubscribe.length; i++) { + unsubscribe[i](); + } }); activate(); @@ -701,7 +726,8 @@ Opens an overlay to show a custom YSOD.
ngShow: "=", model: "=", view: "=", - position: "@" + position: "@", + parentScope: "=?" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js index bfd4888def..2c8887ef3f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js @@ -73,7 +73,15 @@ angular.module("umbraco.directives") if (node.selected) { css.push("umb-tree-node-checked"); } - + + //is this the current action node (this is not the same as the current selected node!) + var actionNode = appState.getMenuState("currentNode"); + if (actionNode) { + if (actionNode.id === node.id) { + css.push("active"); + } + } + return css.join(" "); }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js index 2b4004da11..89f6ebcd6a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js @@ -1,5 +1,4 @@ - -/** +/** @ngdoc directive @name umbraco.directives.directive:umbColorSwatches @restrict E @@ -15,9 +14,10 @@ Use this directive to generate color swatches to pick from. @param {array} colors (attribute): The array of colors. -@param {string} colors (attribute): The array of colors. @param {string} selectedColor (attribute): The selected color. @param {string} size (attribute): The size (s, m). +@param {string} useLabel (attribute): Specify if labels should be used. +@param {string} useColorClass (attribute): Specify if color values are css classes. @param {function} onSelect (expression): Callback function when the item is selected. **/ @@ -28,6 +28,11 @@ Use this directive to generate color swatches to pick from. function link(scope, el, attr, ctrl) { + // Set default to true if not defined + if (angular.isUndefined(scope.useColorClass)) { + scope.useColorClass = false; + } + scope.setColor = function (color) { scope.selectedColor = color; if (scope.onSelect) { @@ -45,7 +50,9 @@ Use this directive to generate color swatches to pick from. colors: '=?', size: '@', selectedColor: '=', - onSelect: '&' + onSelect: '&', + useLabel: '=', + useColorClass: '=?' }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbflatpickr.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbflatpickr.directive.js new file mode 100644 index 0000000000..1f396ab6c2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbflatpickr.directive.js @@ -0,0 +1,231 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbFlatpickr +@restrict E +@scope + +@description +Added in Umbraco version 8.0 +This directive is a wrapper of the flatpickr library. Use it to render a date time picker. +For extra details about options and events take a look here: https://flatpickr.js.org/ + +Use this directive to render a date time picker + +

Markup example

+
+	
+ + + + +
+
+ +

Controller example

+
+	(function () {
+		"use strict";
+
+		function Controller() {
+
+            var vm = this;
+
+            vm.date = "2018-10-10 10:00";
+
+            vm.config = {
+				enableTime: true,
+				dateFormat: "Y-m-d H:i",
+				time_24hr: true
+            };
+
+            vm.datePickerChange = datePickerChange;
+
+            function datePickerChange(selectedDates, dateStr, instance) {
+            	// handle change
+            }
+
+        }
+
+		angular.module("umbraco").controller("My.Controller", Controller);
+
+	})();
+
+ +@param {object} ngModel (binding): Config object for the date picker. +@param {object} options (binding): Config object for the date picker. +@param {callback} onSetup (callback): onSetup gets triggered when the date picker is initialized +@param {callback} onChange (callback): onChange gets triggered when the user selects a date, or changes the time on a selected date. +@param {callback} onOpen (callback): onOpen gets triggered when the calendar is opened. +@param {callback} onClose (callback): onClose gets triggered when the calendar is closed. +@param {callback} onMonthChange (callback): onMonthChange gets triggered when the month is changed, either by the user or programmatically. +@param {callback} onYearChange (callback): onMonthChange gets triggered when the year is changed, either by the user or programmatically. +@param {callback} onReady (callback): onReady gets triggered once the calendar is in a ready state. +@param {callback} onValueUpdate (callback): onValueUpdate gets triggered when the input value is updated with a new date string. +@param {callback} onDayCreate (callback): Take full control of every date cell with theonDayCreate()hook. +**/ + +(function() { + 'use strict'; + + var umbFlatpickr = { + template: '' + + '' + + '
' + + '
', + controller: umbFlatpickrCtrl, + transclude: true, + bindings: { + ngModel: '<', + options: '<', + onSetup: '&?', + onChange: '&?', + onOpen: '&?', + onClose: '&?', + onMonthChange: '&?', + onYearChange: '&?', + onReady: '&?', + onValueUpdate: '&?', + onDayCreate: '&?' + } + }; + + function umbFlatpickrCtrl($element, $timeout, $scope, assetsService) { + var ctrl = this; + var loaded = false; + + ctrl.$onInit = function() { + + // load css file for the date picker + assetsService.loadCss('lib/flatpickr/flatpickr.css', $scope); + + // load the js file for the date picker + assetsService.loadJs('lib/flatpickr/flatpickr.js', $scope).then(function () { + // init date picker + loaded = true; + grabElementAndRunFlatpickr(); + }); + + }; + + function grabElementAndRunFlatpickr() { + $timeout(function() { + var transcludeEl = $element.find('ng-transclude')[0]; + var element = transcludeEl.children[0]; + + setDatepicker(element); + }, 0, true); + } + + function setDatepicker(element) { + var fpLib = flatpickr ? flatpickr : FlatpickrInstance; + + if (!fpLib) { + return console.warn('Unable to find any flatpickr installation'); + } + + setUpCallbacks(); + + var fpInstance = new fpLib(element, ctrl.options); + + if (ctrl.onSetup) { + ctrl.onSetup({ + fpItem: fpInstance + }); + } + + // If has ngModel set the date + if (ctrl.ngModel) { + fpInstance.setDate(ctrl.ngModel); + } + + // destroy the flatpickr instance when the dom element is removed + angular.element(element).on('$destroy', function() { + fpInstance.destroy(); + }); + + // Refresh the scope + $scope.$applyAsync(); + } + + function setUpCallbacks() { + // bind hook for onChange + if(ctrl.options && ctrl.onChange) { + ctrl.options.onChange = function(selectedDates, dateStr, instance) { + $timeout(function() { + ctrl.onChange({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); + }); + }; + } + + // bind hook for onOpen + if(ctrl.options && ctrl.onOpen) { + ctrl.options.onOpen = function(selectedDates, dateStr, instance) { + $timeout(function() { + ctrl.onOpen({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); + }); + }; + } + + // bind hook for onOpen + if(ctrl.options && ctrl.onClose) { + ctrl.options.onClose = function(selectedDates, dateStr, instance) { + $timeout(function() { + ctrl.onClose({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); + }); + }; + } + + // bind hook for onMonthChange + if(ctrl.options && ctrl.onMonthChange) { + ctrl.options.onMonthChange = function(selectedDates, dateStr, instance) { + $timeout(function() { + ctrl.onMonthChange({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); + }); + }; + } + + // bind hook for onYearChange + if(ctrl.options && ctrl.onYearChange) { + ctrl.options.onYearChange = function(selectedDates, dateStr, instance) { + $timeout(function() { + ctrl.onYearChange({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); + }); + }; + } + + // bind hook for onReady + if(ctrl.options && ctrl.onReady) { + ctrl.options.onReady = function(selectedDates, dateStr, instance) { + $timeout(function() { + ctrl.onReady({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); + }); + }; + } + + // bind hook for onValueUpdate + if(ctrl.onValueUpdate) { + ctrl.options.onValueUpdate = function(selectedDates, dateStr, instance) { + $timeout(function() { + ctrl.onValueUpdate({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); + }); + }; + } + + // bind hook for onDayCreate + if(ctrl.onDayCreate) { + ctrl.options.onDayCreate = function(selectedDates, dateStr, instance) { + $timeout(function() { + ctrl.onDayCreate({selectedDates: selectedDates, dateStr: dateStr, instance: instance}); + }); + }; + } + + } + } + + angular.module('umbraco.directives').component('umbFlatpickr', umbFlatpickr); + +})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js index 48291bfa87..87cd84ca40 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js @@ -1,93 +1,92 @@ -(function() { - 'use strict'; +(function () { + 'use strict'; + + angular + .module('umbraco.directives') + .component('umbLayoutSelector', { + templateUrl: 'views/components/umb-layout-selector.html', + controller: LayoutSelectorController, + controllerAs: 'vm', + bindings: { + layouts: '<', + activeLayout: '<', + onLayoutSelect: "&" + } + }); - function LayoutSelectorDirective() { + function LayoutSelectorController($scope, $element) { - function link(scope, el, attr, ctrl) { + var vm = this; - scope.layoutDropDownIsOpen = false; - scope.showLayoutSelector = true; + vm.$onInit = onInit; - function activate() { + vm.layoutDropDownIsOpen = false; + vm.showLayoutSelector = true; + vm.pickLayout = pickLayout; + vm.toggleLayoutDropdown = toggleLayoutDropdown; + vm.closeLayoutDropdown = closeLayoutDropdown; + function onInit() { + activate(); + } + + function closeLayoutDropdown() { + vm.layoutDropDownIsOpen = false; + } + + function toggleLayoutDropdown() { + vm.layoutDropDownIsOpen = !vm.layoutDropDownIsOpen; + } + + function pickLayout(selectedLayout) { + if (vm.onLayoutSelect) { + vm.onLayoutSelect({ layout: selectedLayout }); + vm.layoutDropDownIsOpen = false; + } + } + + function activate() { setVisibility(); + setActiveLayout(vm.layouts); + } - setActiveLayout(scope.layouts); + function setVisibility() { - } + var numberOfAllowedLayouts = getNumberOfAllowedLayouts(vm.layouts); - function setVisibility() { - - var numberOfAllowedLayouts = getNumberOfAllowedLayouts(scope.layouts); - - if(numberOfAllowedLayouts === 1) { - scope.showLayoutSelector = false; + if (numberOfAllowedLayouts === 1) { + vm.showLayoutSelector = false; } - } + } - function getNumberOfAllowedLayouts(layouts) { + function getNumberOfAllowedLayouts(layouts) { var allowedLayouts = 0; for (var i = 0; layouts.length > i; i++) { - var layout = layouts[i]; + var layout = layouts[i]; - if(layout.selected === true) { - allowedLayouts++; - } + if (layout.selected === true) { + allowedLayouts++; + } } return allowedLayouts; - } + } - function setActiveLayout(layouts) { + function setActiveLayout(layouts) { for (var i = 0; layouts.length > i; i++) { - var layout = layouts[i]; - if(layout.path === scope.activeLayout.path) { - layout.active = true; - } + var layout = layouts[i]; + if (layout.path === vm.activeLayout.path) { + layout.active = true; + } } - } - - scope.pickLayout = function(selectedLayout) { - if(scope.onLayoutSelect) { - scope.onLayoutSelect(selectedLayout); - scope.layoutDropDownIsOpen = false; - } - }; - - scope.toggleLayoutDropdown = function() { - scope.layoutDropDownIsOpen = !scope.layoutDropDownIsOpen; - }; - - scope.closeLayoutDropdown = function() { - scope.layoutDropDownIsOpen = false; - }; - - activate(); - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-layout-selector.html', - scope: { - layouts: '=', - activeLayout: '=', - onLayoutSelect: "=" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbLayoutSelector', LayoutSelectorDirective); + } + } })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewlayout.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewlayout.directive.js index d026a3f4e5..2c1cbea962 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewlayout.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewlayout.directive.js @@ -1,37 +1,37 @@ -(function() { - 'use strict'; +(function () { + 'use strict'; - function ListViewLayoutDirective() { + function ListViewLayoutDirective() { - function link(scope, el, attr, ctrl) { + function link(scope, el, attr, ctrl) { - scope.getContent = function(contentId) { - if(scope.onGetContent) { - scope.onGetContent(contentId); - } - }; + scope.getContent = function (contentId) { + if (scope.onGetContent) { + scope.onGetContent({ contentId: contentId}); + } + }; - } + } - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-list-view-layout.html', - scope: { - contentId: '=', - folders: '=', - items: '=', - selection: '=', - options: '=', - entityType: '@', - onGetContent: '=' - }, - link: link - }; + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-list-view-layout.html', + scope: { + contentId: '<', + folders: '<', + items: '<', + selection: '<', + options: '<', + entityType: '@', + onGetContent: '&' + }, + link: link + }; - return directive; - } + return directive; + } - angular.module('umbraco.directives').directive('umbListViewLayout', ListViewLayoutDirective); + angular.module('umbraco.directives').directive('umbListViewLayout', ListViewLayoutDirective); })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index af562e04e3..b14f8418c5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -125,6 +125,14 @@ Use this directive to generate a thumbnail grid of media items. i--; } + if (scope.includeSubFolders !== 'true') { + if (item.parentId !== parseInt(scope.currentFolderId)) { + scope.items.splice(i, 1); + i--; + } + } + + } if (scope.items.length > 0) { @@ -316,7 +324,9 @@ Use this directive to generate a thumbnail grid of media items. itemMaxHeight: "@", itemMinWidth: "@", itemMinHeight: "@", - onlyImages: "@" + onlyImages: "@", + includeSubFolders: "@", + currentFolderId: "@" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index 64ae8f4960..196a28c753 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -58,9 +58,13 @@ entityResource.getPagedChildren(miniListView.node.id, scope.entityType, miniListView.pagination) .then(function (data) { + // update children miniListView.children = data.items; _.each(miniListView.children, function(c) { + // child allowed by default + c.allowed = true; + // convert legacy icon for node if(c.icon) { c.icon = iconHelper.convertFromLegacyIcon(c.icon); @@ -72,6 +76,17 @@ c.published = c.metaData.IsPublished; } } + + // filter items if there is a filter and it's not advanced + // ** ignores advanced filter at the moment + if (scope.entityTypeFilter && !scope.entityTypeFilter.filterAdvanced) { + var a = scope.entityTypeFilter.filter.toLowerCase().replace(/\s/g, '').split(','); + var found = a.indexOf(c.metaData.ContentTypeAlias.toLowerCase()) >= 0; + + if (!scope.entityTypeFilter.filterExclude && !found || scope.entityTypeFilter.filterExclude && found) { + c.allowed = false; + } + } }); // update pagination miniListView.pagination.totalItems = data.totalItems; @@ -87,7 +102,7 @@ }; scope.selectNode = function(node) { - if(scope.onSelect) { + if (scope.onSelect && node.allowed) { scope.onSelect({'node': node}); } }; @@ -184,7 +199,8 @@ entityType: "@", startNodeId: "=", onSelect: "&", - onClose: "&" + onClose: "&", + entityTypeFilter: "=" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js index 2139a14b48..e7abc81841 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js @@ -93,7 +93,7 @@ Use this directive to generate a pagination. function activate() { scope.pagination = []; - + var i = 0; if (scope.totalPages <= 10) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js index 91a0a41a10..e8d8315b7a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js @@ -50,6 +50,11 @@ Use this directive make an element sticky and follow the page when scrolling. function activate() { + if (bar.parents(".umb-property").length > 1) { + bar.addClass("nested"); + return; + } + if (attr.scrollableContainer) { scrollableContainer = $(attr.scrollableContainer); } else { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js index 399ce8877a..ae41073c0d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js @@ -110,77 +110,72 @@ **/ (function () { - 'use strict'; + 'use strict'; - function TableDirective(iconHelper) { + function TableController(iconHelper) { - function link(scope, el, attr, ctrl) { + var vm = this; - scope.clickItem = function (item, $event) { - if (scope.onClick) { - scope.onClick(item); - $event.stopPropagation(); + vm.clickItem = function (item, $event) { + if (vm.onClick) { + vm.onClick({ item: item}); + $event.stopPropagation(); } - }; + }; - scope.selectItem = function (item, $index, $event) { - if (scope.onSelect) { - scope.onSelect(item, $index, $event); - $event.stopPropagation(); + vm.selectItem = function (item, $index, $event) { + if (vm.onSelect) { + vm.onSelect({ item: item, $index: $index, $event: $event }); + $event.stopPropagation(); } - }; + }; - scope.selectAll = function ($event) { - if (scope.onSelectAll) { - scope.onSelectAll($event); + vm.selectAll = function ($event) { + if (vm.onSelectAll) { + vm.onSelectAll({ $event: $event}); } - }; + }; - scope.isSelectedAll = function () { - if (scope.onSelectedAll && scope.items && scope.items.length > 0) { - return scope.onSelectedAll(); + vm.isSelectedAll = function () { + if (vm.onSelectedAll && vm.items && vm.items.length > 0) { + return vm.onSelectedAll(); } - }; + }; - scope.isSortDirection = function (col, direction) { - if (scope.onSortingDirection) { - return scope.onSortingDirection(col, direction); + vm.isSortDirection = function (col, direction) { + if (vm.onSortingDirection) { + return vm.onSortingDirection({ col: col, direction: direction }); } - }; + }; - scope.sort = function (field, allow, isSystem) { - if (scope.onSort) { - scope.onSort(field, allow, isSystem); + vm.sort = function (field, allow, isSystem) { + if (vm.onSort) { + vm.onSort({ field: field, allow: allow, isSystem: isSystem }); } - }; + }; - scope.getIcon = function (entry) { - return iconHelper.convertFromLegacyIcon(entry.icon); - }; + vm.getIcon = function (entry) { + return iconHelper.convertFromLegacyIcon(entry.icon); + }; + } - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-table.html', - scope: { - items: '=', - itemProperties: '=', - allowSelectAll: '=', - onSelect: '=', - onClick: '=', - onSelectAll: '=', - onSelectedAll: '=', - onSortingDirection: '=', - onSort: '=' - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbTable', TableDirective); + angular + .module('umbraco.directives') + .component('umbTable', { + templateUrl: 'views/components/umb-table.html', + controller: TableController, + controllerAs: 'vm', + bindings: { + items: '<', + itemProperties: '<', + allowSelectAll: '<', + onSelect: '&', + onClick: '&', + onSelectAll: '&', + onSelectedAll: '&', + onSortingDirection: '&', + onSort: '&' + } + }); })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js index cf12585e15..0dec3f6e0b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js @@ -221,7 +221,8 @@ isClientSide: true }); - newVal += files[i].name + ","; + //special check for a comma in the name + newVal += files[i].name.replace(',', '-') + ","; if (isImage) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/umbisolateform.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/umbisolateform.directive.js new file mode 100644 index 0000000000..f885a596e1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/umbisolateform.directive.js @@ -0,0 +1,10 @@ +angular.module("umbraco.directives") + .directive('umbIsolateForm', function () { + return { + restrict: 'A', + require: ['form', '^form'], + link: function (scope, element, attrs, forms) { + forms[1].$removeControl(forms[0]); + } + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/showvalidationonsubmit.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/showvalidationonsubmit.directive.js index c46a3a9f9a..3dc48573c7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/showvalidationonsubmit.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/showvalidationonsubmit.directive.js @@ -3,16 +3,20 @@ function showValidationOnSubmit(serverValidationManager) { return { - require: "ngMessages", + require: ["ngMessages", "^^?valFormManager"], restrict: "A", - + scope: { + form: "=?" + }, link: function (scope, element, attr, ctrl) { - //We can either get the form submitted status by the parent directive valFormManager (if we add a property to it) - //or we can just check upwards in the DOM for the css class (easier for now). + var formMgr = ctrl.length > 1 ? ctrl[1] : null; + + //We can either get the form submitted status by the parent directive valFormManager + //or we can check upwards in the DOM for the css class... lets try both :) //The initial hidden state can't always be hidden because when we switch variants in the content editor we cannot //reset the status. - var submitted = element.closest(".show-validation").length > 0; + var submitted = element.closest(".show-validation").length > 0 || (formMgr && formMgr.showValidation); if (!submitted) { element.hide(); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js index 948702a4e3..eaf67bcb91 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js @@ -3,7 +3,7 @@ * @name umbraco.directives.directive:valFormManager * @restrict A * @require formController -* @description Used to broadcast an event to all elements inside this one to notify that form validation has +* @description Used to broadcast an event to all elements inside this one to notify that form validation has * changed. If we don't use this that means you have to put a watch for each directive on a form's validation * changing which would result in much higher processing. We need to actually watch the whole $error collection of a form * because just watching $valid or $invalid doesn't acurrately trigger form validation changing. @@ -19,7 +19,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location var SAVED_EVENT_NAME = "formSubmitted"; return { - require: "form", + require: ["form", "^^?valFormManager", "^^?valSubView"], restrict: "A", controller: function($scope) { //This exposes an API for direct use with this directive @@ -35,6 +35,8 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location })); }; + this.showValidation = $scope.showValidation === true; + //Ensure to remove the event handlers when this instance is destroyted $scope.$on('$destroy', function () { for (var u in unsubscribe) { @@ -42,8 +44,17 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location } }); }, - link: function (scope, element, attr, formCtrl) { + link: function (scope, element, attr, ctrls) { + function notifySubView() { + if (subView){ + subView.valStatusChanged({ form: formCtrl, showValidation: scope.showValidation }); + } + } + + var formCtrl = ctrls[0]; + var parentFormMgr = ctrls.length > 0 ? ctrls[1] : null; + var subView = ctrls.length > 1 ? ctrls[2] : null; var labels = {}; var labelKeys = [ @@ -78,7 +89,9 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location return sum; }, function (e) { scope.$broadcast("valStatusChanged", { form: formCtrl }); - + + notifySubView(); + //find all invalid elements' .control-group's and apply the error class var inError = element.find(".control-group .ng-invalid").closest(".control-group"); inError.addClass("error"); @@ -89,15 +102,17 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location }); - //This tracks if the user is currently saving a new item, we use this to determine + //This tracks if the user is currently saving a new item, we use this to determine // if we should display the warning dialog that they are leaving the page - if a new item // is being saved we never want to display that dialog, this will also cause problems when there // are server side validation issues. var isSavingNewItem = false; //we should show validation if there are any msgs in the server validation collection - if (serverValidationManager.items.length > 0) { + if (serverValidationManager.items.length > 0 || (parentFormMgr && parentFormMgr.showValidation)) { element.addClass(SHOW_VALIDATION_CLASS_NAME); + scope.showValidation = true; + notifySubView(); } var unsubscribe = []; @@ -105,7 +120,8 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location //listen for the forms saving event unsubscribe.push(scope.$on(SAVING_EVENT_NAME, function(ev, args) { element.addClass(SHOW_VALIDATION_CLASS_NAME); - + scope.showValidation = true; + notifySubView(); //set the flag so we can check to see if we should display the error. isSavingNewItem = $routeParams.create; })); @@ -114,9 +130,10 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location unsubscribe.push(scope.$on(SAVED_EVENT_NAME, function(ev, args) { //remove validation class element.removeClass(SHOW_VALIDATION_CLASS_NAME); - + scope.showValidation = false; + notifySubView(); //clear form state as at this point we retrieve new data from the server - //and all validation will have cleared at this point + //and all validation will have cleared at this point formCtrl.$setPristine(); })); @@ -197,7 +214,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location $timeout(function(){ formCtrl.$setPristine(); }, 1000); - + } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valmulti.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valmulti.directive.js new file mode 100644 index 0000000000..1aca4c2528 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valmulti.directive.js @@ -0,0 +1,27 @@ +(function () { + /** + * @ngdoc directive + * @name umbraco.directives.directive:multi + * @restrict A + * @description Used on input fields when you want to validate multiple fields at once. + **/ + function multi($parse, $rootScope) { + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, elem, attrs, ngModelCtrl) { + var validate = $parse(attrs.multi)(scope); + ngModelCtrl.$viewChangeListeners.push(function () { + // ngModelCtrl.$setValidity('multi', validate()); + $rootScope.$broadcast('multi:valueChanged'); + }); + + var deregisterListener = scope.$on('multi:valueChanged', function (event) { + ngModelCtrl.$setValidity('multi', validate()); + }); + scope.$on('$destroy', deregisterListener); // optional, only required for $rootScope.$on + } + }; + } + angular.module('umbraco.directives.validation').directive('multi', ['$parse', '$rootScope', multi]); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js index a65a08d17e..097602fe20 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js @@ -10,6 +10,29 @@ function valSubViewDirective() { + function controller($scope, $element) { + //expose api + return { + valStatusChanged: function(args) { + if (!args.form.$valid) { + var subViewContent = $element.find(".ng-invalid"); + + if (subViewContent.length > 0) { + $scope.model.hasError = true; + $scope.model.errorClass = args.showValidation ? 'show-validation' : null; + } else { + $scope.model.hasError = false; + $scope.model.errorClass = null; + } + } + else { + $scope.model.hasError = false; + $scope.model.errorClass = null; + } + } + } + } + function link(scope, el, attr, ctrl) { //if there are no containing form or valFormManager controllers, then we do nothing @@ -43,7 +66,8 @@ var directive = { require: ['?^^form', '?^^valFormManager'], restrict: "A", - link: link + link: link, + controller: controller }; return directive; diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js index 615a7aeec9..28aae834b3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js @@ -145,7 +145,7 @@ angular.module('umbraco.mocks'). "content_statistics": "Statistics", "content_titleOptional": "Title (optional)", "content_type": "Type", - "content_unPublish": "Unpublish", + "content_unpublish": "Unpublish", "content_updateDate": "Last edited", "content_updateDateDesc": "Date/time this document was created", "content_uploadClear": "Remove file", @@ -644,24 +644,14 @@ angular.module('umbraco.mocks'). "templateEditor_usedIfAllEmpty": "Will only be used when the field values above are empty", "templateEditor_usedIfEmpty": "This field will only be used if the primary field is empty", "templateEditor_withTime": "Yes, with time. Seperator: ", - "translation_assignedTasks": "Tasks assigned to you", - "translation_assignedTasksHelp": " The list below shows translation tasks assigned to you. To see a detailed view including comments, click on 'Details' or just the page name. You can also download the page as XML directly by clicking the 'Download Xml' link.
To close a translation task, please go to the Details view and click the 'Close' button. ", - "translation_closeTask": "close task", "translation_details": "Translation details", - "translation_downloadAllAsXml": "Download all translation tasks as xml", - "translation_downloadTaskAsXml": "Download xml", "translation_DownloadXmlDTD": "Download xml DTD", "translation_fields": "Fields", "translation_includeSubpages": "Include subpages", "translation_mailBody": " Hi %0% This is an automated mail to inform you that the document '%1%' has been requested for translation into '%5%' by %2%. Go to http://%3%/translation/details.aspx?id=%4% to edit. Or log into Umbraco to get an overview of your translation tasks http://%3% Have a nice day! Cheers from the Umbraco robot ", - "translation_mailSubject": "[%0%] Translation task for %1%", "translation_noTranslators": "No translator users found. Please create a translator user before you start sending content to translation", - "translation_ownedTasks": "Tasks created by you", - "translation_ownedTasksHelp": " The list below shows pages created by you. To see a detailed view including comments, click on 'Details' or just the page name. You can also download the page as XML directly by clicking the 'Download Xml' link. To close a translation task, please go to the Details view and click the 'Close' button. ", - "translation_pageHasBeenSendToTranslation": "The page '%0%' has been send to translation", + "translation_pageHasBeenSendToTranslation": "The page '%0%' has been send to translation", "translation_sendToTranslate": "Send the page '%0%' to translation", - "translation_taskAssignedBy": "Assigned by", - "translation_taskOpened": "Task opened", "translation_totalWords": "Total words", "translation_translateTo": "Translate to", "translation_translationDone": "Translation completed.", diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 5fb6fe1625..721cd4da57 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -19,19 +19,21 @@ * contentResource.getById(1234) * .then(function(data) { * $scope.content = data; - * }); - * + * }); + * **/ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { /** internal method process the saving of data and post processing the result */ - function saveContentItem(content, action, files, restApiUrl) { + function saveContentItem(content, action, files, restApiUrl, showNotifications) { + return umbRequestHelper.postSaveContent({ restApiUrl: restApiUrl, content: content, action: action, files: files, + showNotifications: showNotifications, dataFormatter: function (c, a) { return umbDataFormatter.formatContentPostData(c, a); } @@ -91,7 +93,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * $scope.complete = true; * }); - * + * * @param {Object} args arguments object * @param {Int} args.parentId the ID of the parent node * @param {Array} options.sortedIds array of node IDs as they should be sorted @@ -132,9 +134,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert("node was moved"); * }, function(err){ - * alert("node didnt move:" + err.data.Message); + * alert("node didnt move:" + err.data.Message); * }); - * + * * @param {Object} args arguments object * @param {Int} args.idd the ID of the node to move * @param {Int} args.parentId the ID of the parent node to move to @@ -175,9 +177,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert("node was copied"); * }, function(err){ - * alert("node wasnt copy:" + err.data.Message); + * alert("node wasnt copy:" + err.data.Message); * }); - * + * * @param {Object} args arguments object * @param {Int} args.id the ID of the node to copy * @param {Int} args.parentId the ID of the parent node to copy to @@ -204,7 +206,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { /** * @ngdoc method - * @name umbraco.resources.contentResource#unPublish + * @name umbraco.resources.contentResource#unpublish * @methodOf umbraco.resources.contentResource * * @description @@ -212,34 +214,71 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * ##usage *
-          * contentResource.unPublish(1234)
+          * contentResource.unpublish(1234)
           *    .then(function() {
           *        alert("node was unpulished");
           *    }, function(err){
-          *      alert("node wasnt unpublished:" + err.data.Message); 
+          *      alert("node wasnt unpublished:" + err.data.Message);
           *    });
-          * 
+ * * @param {Int} id the ID of the node to unpublish * @returns {Promise} resourcePromise object. * */ - unPublish: function (id, culture) { + unpublish: function (id, cultures) { if (!id) { throw "id cannot be null"; } - if (!culture) { - culture = null; + if (!cultures) { + cultures = []; } return umbRequestHelper.resourcePromise( $http.post( umbRequestHelper.getApiUrl( "contentApiBaseUrl", - "PostUnPublish", - { id: id, culture: culture })), + "PostUnpublish"), { id: id, cultures: cultures }), 'Failed to publish content with id ' + id); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getCultureAndDomains + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets the culture and hostnames for a content item with the given Id + * + * ##usage + *
+          * contentResource.getCultureAndDomains(1234)
+          *    .then(function(data) {
+          *        alert(data.Domains, data.Language);
+          *    });
+          * 
+ * @param {Int} id the ID of the node to get the culture and domains for. + * @returns {Promise} resourcePromise object. + * + */ + getCultureAndDomains: function (id) { + if (!id) { + throw "id cannot be null"; + } + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetCultureAndDomains", { id: id })), + 'Failed to retreive culture and hostnames for ' + id); + }, + saveLanguageAndDomains: function (model) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSaveLanguageAndDomains"), + model)); + }, /** * @ngdoc method * @name umbraco.resources.contentResource#emptyRecycleBin @@ -254,8 +293,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('its empty!'); * }); - * - * + * + * * @returns {Promise} resourcePromise object. * */ @@ -282,9 +321,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('its gone!'); * }); - * - * - * @param {Int} id id of content item to delete + * + * + * @param {Int} id id of content item to delete * @returns {Promise} resourcePromise object. * */ @@ -320,38 +359,38 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * contentResource.getById(1234)
           *    .then(function(content) {
-          *        var myDoc = content; 
+          *        var myDoc = content;
           *        alert('its here!');
           *    });
-          * 
- * + * + * * @param {Int} id id of content item to return - * @param {Int} culture optional culture to retrieve the item in + * @param {Int} culture optional culture to retrieve the item in * @returns {Promise} resourcePromise object containing the content item. * */ getById: function (id) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetById", - { id: id })), - 'Failed to retrieve data for content id ' + id) - .then(function(result) { + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetById", + { id: id })), + 'Failed to retrieve data for content id ' + id) + .then(function (result) { return $q.when(umbDataFormatter.formatContentGetData(result)); }); }, getBlueprintById: function (id) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetBlueprintById", - [{ id: id }])), - 'Failed to retrieve data for content id ' + id) - .then(function(result) { + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetBlueprintById", + [{ id: id }])), + 'Failed to retrieve data for content id ' + id) + .then(function (result) { return $q.when(umbDataFormatter.formatContentGetData(result)); }); }, @@ -391,12 +430,12 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * contentResource.getByIds( [1234,2526,28262])
           *    .then(function(contentArray) {
-          *        var myDoc = contentArray; 
+          *        var myDoc = contentArray;
           *        alert('they are here!');
           *    });
-          * 
- * - * @param {Array} ids ids of content items to return as an array + * + * + * @param {Array} ids ids of content items to return as an array * @returns {Promise} resourcePromise object containing the content items array. * */ @@ -408,15 +447,15 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }); return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for content with multiple ids') + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for content with multiple ids') .then(function (result) { //each item needs to be re-formatted - _.each(result, function(r) { + _.each(result, function (r) { umbDataFormatter.formatContentGetData(r) }); return $q.when(result); @@ -431,41 +470,41 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. - * + * * - Parent Id must be provided so umbraco knows where to store the content - * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold - * + * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold + * * The scaffold is used to build editors for content that has not yet been populated with data. - * + * * ##usage *
           * contentResource.getScaffold(1234, 'homepage')
           *    .then(function(scaffold) {
           *        var myDoc = scaffold;
-          *        myDoc.name = "My new document"; 
+          *        myDoc.name = "My new document";
           *
           *        contentResource.publish(myDoc, true)
           *            .then(function(content){
           *                alert("Retrieved, updated and published again");
           *            });
           *    });
-          * 
- * + * + * * @param {Int} parentId id of content item to return - * @param {String} alias contenttype alias to base the scaffold on + * @param {String} alias contenttype alias to base the scaffold on * @returns {Promise} resourcePromise object containing the content scaffold. * */ getScaffold: function (parentId, alias) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty content item type ' + alias) - .then(function(result) { + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty content item type ' + alias) + .then(function (result) { return $q.when(umbDataFormatter.formatContentGetData(result)); }); }, @@ -473,13 +512,13 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { getBlueprintScaffold: function (parentId, blueprintId) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ blueprintId: blueprintId }, { parentId: parentId }])), - 'Failed to retrieve blueprint for id ' + blueprintId) - .then(function(result) { + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ blueprintId: blueprintId }, { parentId: parentId }])), + 'Failed to retrieve blueprint for id ' + blueprintId) + .then(function (result) { return $q.when(umbDataFormatter.formatContentGetData(result)); }); }, @@ -498,8 +537,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function(url) { * alert('its here!'); * }); - * - * + * + * * @param {Int} id Id of node to return the public url to * @returns {Promise} resourcePromise object containing the url. * @@ -526,11 +565,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
           *    .then(function(contentArray) {
-          *        var children = contentArray; 
+          *        var children = contentArray;
           *        alert('they are here!');
           *    });
-          * 
- * + * + * * @param {Int} parentid id of content item to return children of * @param {Object} options optional options object * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 @@ -538,6 +577,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @param {String} options.filter if provided, query will only return those with names matching the filter * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @param {String} options.cultureName if provided, the results will be for this specific culture/variant * @returns {Promise} resourcePromise object containing an array of content items. * */ @@ -547,10 +587,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { includeProperties: [], pageSize: 0, pageNumber: 0, - filter: '', + filter: "", orderDirection: "Ascending", orderBy: "SortOrder", - orderBySystemField: true + orderBySystemField: true, + cultureName: "" }; if (options === undefined) { options = {}; @@ -594,7 +635,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { orderBy: options.orderBy, orderDirection: options.orderDirection, orderBySystemField: toBool(options.orderBySystemField), - filter: options.filter + filter: options.filter, + cultureName: options.cultureName })), 'Failed to retrieve children for content item ' + parentId); }, @@ -615,9 +657,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * * ##usage *
           * contentResource.getById(1234)
@@ -628,26 +670,27 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
           *                alert("Retrieved, updated and saved again");
           *            });
           *    });
-          * 
- * + * + * * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @param {Bool} showNotifications an option to disable/show notifications (default is true) * @returns {Promise} resourcePromise object containing the saved content item. * */ - save: function (content, isNew, files) { + save: function (content, isNew, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", "PostSave"); - return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); + return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint, showNotifications); }, - saveBlueprint: function (content, isNew, files) { + saveBlueprint: function (content, isNew, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", "PostSaveBlueprint"); - return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); + return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint, showNotifications); }, /** @@ -657,9 +700,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * * ##usage *
           * contentResource.getById(1234)
@@ -670,19 +713,20 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
           *                alert("Retrieved, updated and published again");
           *            });
           *    });
-          * 
- * + * + * * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @param {Bool} showNotifications an option to disable/show notifications (default is true) * @returns {Promise} resourcePromise object containing the saved content item. * */ - publish: function (content, isNew, files) { + publish: function (content, isNew, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", "PostSave"); - return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint); + return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint, showNotifications); }, @@ -693,7 +737,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves changes made to a content item, and notifies any subscribers about a pending publication - * + * * ##usage *
           * contentResource.getById(1234)
@@ -704,11 +748,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
           *                alert("Retrieved, updated and notication send off");
           *            });
           *    });
-          * 
- * + * + * * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document * @returns {Promise} resourcePromise object containing the saved content item. * */ @@ -726,15 +770,15 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Publishes a content item with a given ID - * + * * ##usage *
           * contentResource.publishById(1234)
           *    .then(function(content) {
           *        alert("published");
           *    });
-          * 
- * + * + * * @param {Int} id The ID of the conten to publish * @returns {Promise} resourcePromise object containing the published content item. * diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index 56dff12985..86867ccff9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -298,10 +298,10 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca }, - createCollection: function (parentId, collectionName, collectionItemName, collectionIcon, collectionItemIcon) { + createCollection: function (parentId, collectionName, collectionCreateTemplate, collectionItemName, collectionItemCreateTemplate, collectionIcon, collectionItemIcon) { return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateCollection", { parentId: parentId, collectionName: collectionName, collectionItemName: collectionItemName, collectionIcon: collectionIcon, collectionItemIcon: collectionItemIcon })), + $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateCollection", { parentId: parentId, collectionName: collectionName, collectionCreateTemplate: collectionCreateTemplate, collectionItemName: collectionItemName, collectionItemCreateTemplate: collectionItemCreateTemplate, collectionIcon: collectionIcon, collectionItemIcon: collectionItemIcon})), 'Failed to create collection under ' + parentId); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 6647c6fb7f..f864696873 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -288,17 +288,19 @@ function entityResource($q, $http, umbRequestHelper) { * Gets ancestor entities for a given item * * - * @param {string} type Object type name + * @param {string} type Object type name + * @param {string} culture Culture * @returns {Promise} resourcePromise object containing the entity. * */ - getAncestors: function (id, type) { + getAncestors: function (id, type, culture) { + if (culture === undefined) culture = ""; return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetAncestors", - [{id: id}, {type: type}])), + [{ id: id }, { type: type }, { culture: culture }])), 'Failed to retrieve ancestor data for id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js index 5eeaf5644b..dad5345e6c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js @@ -7,10 +7,60 @@ **/ function logResource($q, $http, umbRequestHelper) { + function isValidDate(input) { + if (input) { + if (Object.prototype.toString.call(input) === "[object Date]" && !isNaN(input.getTime())) { + return true; + } + } + + return false; + }; + + function dateToValidIsoString(input) { + if (isValidDate(input)) { + return input.toISOString(); + } + + return ''; + }; + //the factory object returned return { - getPagedEntityLog: function (options) { + /** + * @ngdoc method + * @name umbraco.resources.logResource#getPagedEntityLog + * @methodOf umbraco.resources.logResource + * + * @description + * Gets a paginated log history for a entity + * + * ##usage + *
+        * var options = {
+        *      id : 1234
+        *      pageSize : 10,
+        *      pageNumber : 1,
+        *      orderDirection : "Descending",
+        *      sinceDate : new Date(2018,0,1)
+        * };
+        * logResource.getPagedEntityLog(options)
+        *    .then(function(log) {
+        *        alert('its here!');
+        *    });
+        * 
+ * + * @param {Object} options options object + * @param {Int} options.id the id of the entity + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 10, set to 0 to disable paging + * @param {Int} options.pageNumber if paging data, current page index, default = 1 + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Descending` + * @param {Date} options.sinceDate if provided this will only get log entries going back to this date + * @returns {Promise} resourcePromise object containing the log. + * + */ + getPagedEntityLog: function(options) { var defaults = { pageSize: 10, @@ -24,11 +74,15 @@ function logResource($q, $http, umbRequestHelper) { angular.extend(defaults, options); //now copy back to the options we will use options = defaults; + + if (options.hasOwnProperty('sinceDate')) { + options.sinceDate = dateToValidIsoString(options.sinceDate); + } + //change asc/desct if (options.orderDirection === "asc") { options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { + } else if (options.orderDirection === "desc") { options.orderDirection = "Descending"; } @@ -45,7 +99,37 @@ function logResource($q, $http, umbRequestHelper) { 'Failed to retrieve log data for id'); }, - getPagedUserLog: function (options) { + /** + * @ngdoc method + * @name umbraco.resources.logResource#getPagedUserLog + * @methodOf umbraco.resources.logResource + * + * @description + * Gets a paginated log history for the current user + * + * ##usage + *
+         * var options = {
+         *      pageSize : 10,
+         *      pageNumber : 1,
+         *      orderDirection : "Descending",
+         *      sinceDate : new Date(2018,0,1)
+         * };
+         * logResource.getPagedUserLog(options)
+         *    .then(function(log) {
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {Object} options options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 10, set to 0 to disable paging + * @param {Int} options.pageNumber if paging data, current page index, default = 1 + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Descending` + * @param {Date} options.sinceDate if provided this will only get log entries going back to this date + * @returns {Promise} resourcePromise object containing the log. + * + */ + getPagedUserLog: function(options) { var defaults = { pageSize: 10, @@ -59,11 +143,15 @@ function logResource($q, $http, umbRequestHelper) { angular.extend(defaults, options); //now copy back to the options we will use options = defaults; + + if (options.hasOwnProperty('sinceDate')) { + options.sinceDate = dateToValidIsoString(options.sinceDate); + } + //change asc/desct if (options.orderDirection === "asc") { options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { + } else if (options.orderDirection === "desc") { options.orderDirection = "Descending"; } @@ -71,7 +159,7 @@ function logResource($q, $http, umbRequestHelper) { $http.get( umbRequestHelper.getApiUrl( "logApiBaseUrl", - "GetPagedEntityLog", + "GetPagedCurrentUserLog", options)), 'Failed to retrieve log data for id'); }, @@ -82,6 +170,7 @@ function logResource($q, $http, umbRequestHelper) { * @methodOf umbraco.resources.logResource * * @description + * [OBSOLETE] use getPagedEntityLog instead
* Gets the log history for a give entity id * * ##usage @@ -96,23 +185,24 @@ function logResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the log. * */ - getEntityLog: function (id) { + getEntityLog: function(id) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "logApiBaseUrl", - "GetEntityLog", - [{ id: id }])), - 'Failed to retrieve user data for id ' + id); + $http.get( + umbRequestHelper.getApiUrl( + "logApiBaseUrl", + "GetEntityLog", + [{ id: id }])), + 'Failed to retrieve user data for id ' + id); }, - + /** * @ngdoc method * @name umbraco.resources.logResource#getUserLog * @methodOf umbraco.resources.logResource * * @description - * Gets the current users' log history for a given type of log entry + * [OBSOLETE] use getPagedUserLog instead
+ * Gets the current user's log history for a given type of log entry * * ##usage *
@@ -127,14 +217,14 @@ function logResource($q, $http, umbRequestHelper) {
          * @returns {Promise} resourcePromise object containing the log.
          *
          */
-        getUserLog: function (type, since) {            
+        getUserLog: function(type, since) {
             return umbRequestHelper.resourcePromise(
-               $http.get(
-                   umbRequestHelper.getApiUrl(
-                       "logApiBaseUrl",
-                       "GetCurrentUserLog",
-                       [{ logtype: type}, {sinceDate: since }])),
-               'Failed to retrieve log data for current user of type ' + type + ' since ' + since);
+                $http.get(
+                    umbRequestHelper.getApiUrl(
+                        "logApiBaseUrl",
+                        "GetCurrentUserLog",
+                        [{ logtype: type }, { sinceDate:  dateToValidIsoString(since) }])),
+                'Failed to retrieve log data for current user of type ' + type + ' since ' + since);
         },
 
         /**
@@ -158,16 +248,16 @@ function logResource($q, $http, umbRequestHelper) {
          * @returns {Promise} resourcePromise object containing the log.
          *
          */
-        getLog: function (type, since) {            
+        getLog: function(type, since) {
             return umbRequestHelper.resourcePromise(
-               $http.get(
-                   umbRequestHelper.getApiUrl(
-                       "logApiBaseUrl",
-                       "GetLog",
-                       [{ logtype: type}, {sinceDate: since }])),
-               'Failed to retrieve log data of type ' + type + ' since ' + since);
-        }
-    };
+                $http.get(
+                    umbRequestHelper.getApiUrl(
+                        "logApiBaseUrl",
+                        "GetLog",
+                        [{ logtype: type }, { sinceDate: dateToValidIsoString(since) }])),
+                'Failed to retrieve log data of type ' + type + ' since ' + since);
+        }      
+};
 }
 
 angular.module('umbraco.resources').factory('logResource', logResource);
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js
index 554134d017..8fd6836884 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js
@@ -104,52 +104,52 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) {
          * @returns {Int} the ID assigned to the saved package manifest
          *
          */ 
-        import: function (package) {
+        import: function (umbPackage) {
            
             return umbRequestHelper.resourcePromise(
                 $http.post(
                   umbRequestHelper.getApiUrl(
                       "packageInstallApiBaseUrl",
-                      "Import"), package),
+                      "Import"), umbPackage),
               'Failed to install package. Error during the step "Import" ');
         }, 
 
-        installFiles: function (package) {
+        installFiles: function (umbPackage) {
             return umbRequestHelper.resourcePromise(
                 $http.post(
                   umbRequestHelper.getApiUrl(
                       "packageInstallApiBaseUrl",
-                      "InstallFiles"), package),
+                      "InstallFiles"), umbPackage),
               'Failed to install package. Error during the step "InstallFiles" ');
         }, 
 
-        checkRestart: function (package) {
+        checkRestart: function (umbPackage) {
 
           return umbRequestHelper.resourcePromise(
             $http.post(
               umbRequestHelper.getApiUrl(
                 "packageInstallApiBaseUrl",
-                "CheckRestart"), package),
+                "CheckRestart"), umbPackage),
             'Failed to install package. Error during the step "CheckRestart" ');
         }, 
 
-        installData: function (package) {
+        installData: function (umbPackage) {
            
             return umbRequestHelper.resourcePromise(
                 $http.post(
                   umbRequestHelper.getApiUrl(
                       "packageInstallApiBaseUrl",
-                      "InstallData"), package),
+                      "InstallData"), umbPackage),
               'Failed to install package. Error during the step "InstallData" ');
         }, 
 
-        cleanUp: function (package) {
+        cleanUp: function (umbPackage) {
            
             return umbRequestHelper.resourcePromise(
                 $http.post(
                   umbRequestHelper.getApiUrl(
                       "packageInstallApiBaseUrl",
-                      "CleanUp"), package),
+                      "CleanUp"), umbPackage),
               'Failed to install package. Error during the step "CleanUp" ');
         }
     };
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/redirecturls.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/redirecturls.resource.js
index ea40c066f0..fb145418ed 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/redirecturls.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/redirecturls.resource.js
@@ -40,6 +40,32 @@
                         { searchTerm: searchTerm, page: pageIndex, pageSize: pageSize })),
                 'Failed to retrieve data for searching redirect urls');
         }
+        /**
+   * @ngdoc function
+   * @name umbraco.resources.redirectUrlResource#getRedirectsForContentItem
+   * @methodOf umbraco.resources.redirectUrlResource
+   * @function
+   *
+   * @description
+   * Used to retrieve RedirectUrls for a specific item of content for Information tab
+   * ##usage
+   * 
+   * redirectUrlsResource.getRedirectsForContentItem("udi:123456")
+   *    .then(function(response) {
+   *
+   *    });
+   * 
+ * @param {String} contentUdi identifier for the content item to retrieve redirects for + */ + function getRedirectsForContentItem(contentUdi) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "redirectUrlManagementApiBaseUrl", + "RedirectUrlsForContentItem", + { contentUdi: contentUdi })), + 'Failed to retrieve redirects for content: ' + contentUdi); + } function getEnableState() { @@ -50,7 +76,7 @@ "GetEnableState")), 'Failed to retrieve data to check if the 301 redirect is enabled'); } - + /** * @ngdoc function * @name umbraco.resources.redirectUrlResource#deleteRedirectUrl @@ -107,7 +133,8 @@ searchRedirectUrls: searchRedirectUrls, deleteRedirectUrl: deleteRedirectUrl, toggleUrlTracker: toggleUrlTracker, - getEnableState: getEnableState + getEnableState: getEnableState, + getRedirectsForContentItem: getRedirectsForContentItem }; return resource; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js index 085ba52b7e..d1ac3d39cf 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/appstate.service.js @@ -74,6 +74,11 @@ function appState(eventsService) { showMenu: null }; + var searchState = { + //Whether the search is being shown or not + show: null + }; + var drawerState = { //this view to show view: null, @@ -221,6 +226,35 @@ function appState(eventsService) { setState(menuState, key, value, "menuState"); }, + /** + * @ngdoc function + * @name umbraco.services.angularHelper#getSearchState + * @methodOf umbraco.services.appState + * @function + * + * @description + * Returns the current search state value by key - we do not return an object here - we do NOT want this + * to be publicly mutable and allow setting arbitrary values + * + */ + getSearchState: function (key) { + return getState(searchState, key, "searchState"); + }, + + /** + * @ngdoc function + * @name umbraco.services.angularHelper#setSearchState + * @methodOf umbraco.services.appState + * @function + * + * @description + * Sets a section state value by key + * + */ + setSearchState: function (key, value) { + setState(searchState, key, value, "searchState"); + }, + /** * @ngdoc function * @name umbraco.services.angularHelper#getDrawerState diff --git a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js index 39d9b686c7..a331af899b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js @@ -65,17 +65,14 @@ angular.module('umbraco.services') return path; } - /** - * Loads in moment.js requirements during the _loadInitAssets call - */ - function loadMomentLocaleForCurrentUser() { + function getMomentLocales(locales, supportedLocales) { - var self = this; - - function loadLocales(currentUser, supportedLocales) { - var locale = currentUser.locale.toLowerCase(); + var localeUrls = []; + var locales = locales.split(','); + for (var i = 0; i < locales.length; i++) { + var locale = locales[i].toString().toLowerCase(); if (locale !== 'en-us') { - var localeUrls = []; + if (supportedLocales.indexOf(locale + '.js') > -1) { localeUrls.push('lib/moment/' + locale + '.js'); } @@ -85,16 +82,35 @@ angular.module('umbraco.services') localeUrls.push('lib/moment/' + majorLocale); } } - return self.load(localeUrls, $rootScope); - } - else { - $q.when(true); } } + return localeUrls; + } + + /** + * Loads specific Moment.js Locales. + * @param {any} locales + * @param {any} supportedLocales + */ + function loadLocales(locales, supportedLocales) { + var localeUrls = getMomentLocales(locales, supportedLocales); + if (localeUrls.length >= 1) { + return assetsService.load(localeUrls, $rootScope); + } + else { + $q.when(true); + } + } + + /** + * Loads in moment.js requirements during the _loadInitAssets call + */ + function loadMomentLocaleForCurrentUser() { + userService.getCurrentUser().then(function (currentUser) { return javascriptLibraryResource.getSupportedLocalesForMoment().then(function (supportedLocales) { - return loadLocales(currentUser, supportedLocales); + return loadLocales(currentUser.locale, supportedLocales); }); }); @@ -136,7 +152,9 @@ angular.module('umbraco.services') return $q.when(true); } }, - + + loadLocales: loadLocales, + /** * @ngdoc method * @name umbraco.services.assetsService#loadCss diff --git a/src/Umbraco.Web.UI.Client/src/common/services/backdrop.service.js b/src/Umbraco.Web.UI.Client/src/common/services/backdrop.service.js index e463845a1c..4f977cb1b2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/backdrop.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/backdrop.service.js @@ -58,8 +58,11 @@ * */ function close() { - args.element = null; - args.show = false; + args.opacity = null, + args.element = null, + args.elementPreventClick = false, + args.disableEventsOnClick = false, + args.show = false eventsService.emit("appState.backdrop", args); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 7b80694e3b..777b336447 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -66,7 +66,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica args.scope.busy = true; - return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles()) + return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles(), args.showNotifications) .then(function (data) { formHelper.resetForm({ scope: args.scope }); @@ -146,7 +146,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica if (!args.methods) { throw "args.methods is not defined"; } - if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.save || !args.methods.unPublish) { + if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.unpublish || !args.methods.schedulePublish) { throw "args.methods does not contain all required defined methods"; } @@ -161,11 +161,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //publish action return { letter: ch, - labelKey: args.content.variants && args.content.variants.length > 1 ? "buttons_saveAndPublishMany" : "buttons_saveAndPublish", + labelKey: "buttons_saveAndPublish", handler: args.methods.saveAndPublish, hotKey: "ctrl+p", hotKeyWhenHidden: true, - alias: "saveAndPublish" + alias: "saveAndPublish", + addEllipsis: args.content.variants && args.content.variants.length > 1 ? "true" : "false" }; case "H": //send to publish @@ -175,27 +176,29 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica handler: args.methods.sendToPublish, hotKey: "ctrl+p", hotKeyWhenHidden: true, - alias: "sendToPublish" - }; - case "A": - //save - return { - letter: ch, - labelKey: "buttons_save", - handler: args.methods.save, - hotKey: "ctrl+s", - hotKeyWhenHidden: true, - alias: "save" + alias: "sendToPublish", + addEllipsis: args.content.variants && args.content.variants.length > 1 ? "true" : "false" }; case "Z": //unpublish return { letter: ch, - labelKey: "content_unPublish", - handler: args.methods.unPublish, + labelKey: "content_unpublish", + handler: args.methods.unpublish, hotKey: "ctrl+u", hotKeyWhenHidden: true, - alias: "unpublish" + alias: "unpublish", + addEllipsis: args.content.variants && args.content.variants.length > 1 ? "true" : "false" + }; + case "SCHEDULE": + //schedule publish - schedule doesn't have a permission letter so + // the button letter is made unique so it doesn't collide with anything else + return { + letter: ch, + labelKey: "buttons_schedulePublish", + handler: args.methods.schedulePublish, + alias: "schedulePublish", + addEllipsis: "true" }; default: return null; @@ -206,8 +209,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica buttons.subButtons = []; //This is the ideal button order but depends on circumstance, we'll use this array to create the button list - // Publish, SendToPublish, Save - var buttonOrder = ["U", "H", "A"]; + // Publish, SendToPublish + var buttonOrder = ["U", "H", "SCHEDULE"]; //Create the first button (primary button) //We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item. @@ -222,6 +225,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica break; } } + //Here's the special check, if the button still isn't set and we are creating and they have create access //we need to add the Save button if (!buttons.defaultButton && args.create && _.contains(args.content.allowedActions, "C")) { @@ -244,51 +248,24 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } } + // if publishing is allowed also allow schedule publish + // we add this manually becuase it doesn't have a permission so it wont + // get picked up by the loop through permissions + if( _.contains(args.content.allowedActions, "U")) { + buttons.subButtons.push(createButtonDefinition("SCHEDULE")); + } // if we are not creating, then we should add unpublish too, // so long as it's already published and if the user has access to publish // and the user has access to unpublish (may have been removed via Event) if (!args.create) { - if (args.content.publishDate && _.contains(args.content.allowedActions, "U") && _.contains(args.content.allowedActions, "Z")) { + var hasPublishedVariant = args.content.variants.filter(function(variant) { return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); }).length > 0; + if (hasPublishedVariant && _.contains(args.content.allowedActions, "U") && _.contains(args.content.allowedActions, "Z")) { buttons.subButtons.push(createButtonDefinition("Z")); } } } - // If we have a scheduled publish or unpublish date change the default button to - // "save" and update the label to "save and schedule - if (args.content.releaseDate || args.content.removeDate) { - - // if save button is alread the default don't change it just update the label - if (buttons.defaultButton && buttons.defaultButton.letter === "A") { - buttons.defaultButton.labelKey = "buttons_saveAndSchedule"; - return; - } - - if (buttons.defaultButton && buttons.subButtons && buttons.subButtons.length > 0) { - // save a copy of the default so we can push it to the sub buttons later - var defaultButtonCopy = angular.copy(buttons.defaultButton); - var newSubButtons = []; - - // if save button is not the default button - find it and make it the default - angular.forEach(buttons.subButtons, function (subButton) { - - if (subButton.letter === "A") { - buttons.defaultButton = subButton; - buttons.defaultButton.labelKey = "buttons_saveAndSchedule"; - } else { - newSubButtons.push(subButton); - } - - }); - - // push old default button into subbuttons - newSubButtons.push(defaultButtonCopy); - buttons.subButtons = newSubButtons; - } - - } - return buttons; }, @@ -409,8 +386,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica case "Z": return { letter: ch, - labelKey: "content_unPublish", - handler: "unPublish" + labelKey: "content_unpublish", + handler: "unpublish" }; default: @@ -439,8 +416,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica var shouldIgnore = function (propName) { return _.some([ "variants", - "notifications", - "ModelState", + "tabs", "properties", "apps", @@ -599,8 +575,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //add model state errors to notifications if (args.showNotifications) { - for (var e in modelState) { - notificationsService.error("Validation", modelState[e][0]); + for (var e in args.err.data.ModelState) { + notificationsService.error("Validation", args.err.data.ModelState[e][0]); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 3ad2864fa8..cb416e3974 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -85,6 +85,7 @@ function navigationService($rootScope, $route, $routeParams, $log, $location, $q appState.setSectionState("showSearchResults", false); appState.setGlobalState("stickyNavigation", false); appState.setGlobalState("showTray", false); + appState.setMenuState("currentNode", null); if (appState.getGlobalState("isTablet") === true) { appState.setGlobalState("showNavigation", false); @@ -204,6 +205,29 @@ function navigationService($rootScope, $route, $routeParams, $log, $location, $q }); }, + /** + * @ngdoc method + * @name umbraco.services.navigationService#retainQueryStrings + * @methodOf umbraco.services.navigationService + * + * @description + * Will check the next route parameters to see if any of the query strings that should be retained from the previous route are missing, + * if they are they will be merged and an object containing all route parameters is returned. If nothing should be changed, then null is returned. + * @param {Object} currRouteParams The current route parameters + * @param {Object} nextRouteParams The next route parameters + */ + retainQueryStrings: function (currRouteParams, nextRouteParams) { + var toRetain = angular.copy(nextRouteParams); + var updated = false; + _.each(retainedQueryStrings, function (r) { + if (currRouteParams[r] && !nextRouteParams[r]) { + toRetain[r] = currRouteParams[r]; + updated = true; + } + }); + return updated ? toRetain : null; + }, + /** * @ngdoc method * @name umbraco.services.navigationService#load @@ -342,7 +366,8 @@ function navigationService($rootScope, $route, $routeParams, $log, $location, $q if (appState.getGlobalState("isTablet") === true && !appState.getGlobalState("stickyNavigation")) { //reset it to whatever is in the url - appState.setSectionState("currentSection", $routeParams.section); + appState.setSectionState("currentSection", $routeParams.section); + setMode("default-hidesectiontree"); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 127124ea13..27874cef44 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -761,13 +761,13 @@ function tinyMceService($log, imageHelper, $http, $timeout, macroResource, macro * @param {string} input the string to parse */ getAnchorNames: function (input) { - var anchors = []; - if (!input) { - return anchors; - } - + var anchors = []; + if (!input) { + return anchors; + } + var anchorPattern = //gi; - var matches = input.match(anchorPattern); + var matches = input.match(anchorPattern); if (matches) { @@ -776,7 +776,9 @@ function tinyMceService($log, imageHelper, $http, $timeout, macroResource, macro }); } - return anchors; + return anchors.filter(function(val, i, self) { + return self.indexOf(val) === i; + }); }, insertLinkInEditor: function (editor, target, anchorElm) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index 228c885529..1619ca0623 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -136,8 +136,8 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ //create the callbacs based on whats been passed in. var callbacks = { - success: ((!opts || !opts.success) ? defaultSuccess : opts.success), - error: ((!opts || !opts.error) ? defaultError : opts.error) + success: (!opts || !opts.success) ? defaultSuccess : opts.success, + error: (!opts || !opts.error ? defaultError : opts.error) }; return httpPromise.then(function (response) { @@ -156,7 +156,7 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ //this is a JS/angular error that we should deal with return $q.reject({ errorMsg: response.message - }) + }); } //invoke the callback @@ -188,12 +188,22 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ errorMsg: result.errorMsg, data: result.data, status: result.status - }) + }); }); }, - /** Used for saving content/media/members specifically */ + /** + * @ngdoc method + * @name umbraco.resources.contentResource#postSaveContent + * @methodOf umbraco.resources.contentResource + * + * @description + * Used for saving content/media/members specifically + * + * @param {Object} args arguments object + * @returns {Promise} http promise object. + */ postSaveContent: function (args) { if (!args.restApiUrl) { @@ -211,6 +221,9 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ if (!args.dataFormatter) { throw "args.dataFormatter is a required argument"; } + if (args.showNotifications === null || args.showNotifications === undefined) { + args.showNotifications = true; + } //save the active tab id so we can set it when the data is returned. var activeTab = _.find(args.content.tabs, function (item) { @@ -246,7 +259,9 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ response.data.tabs[activeTabIndex].active = true; } - formHelper.showNotifications(response.data); + if (args.showNotifications) { + formHelper.showNotifications(response.data); + } //TODO: Do we need to pass the result through umbDataFormatter.formatContentGetData? Right now things work so not sure but we should check @@ -278,7 +293,7 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ } } - else { + else if (args.showNotifications) { formHelper.showNotifications(response.data); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 5b723d9328..ac35790127 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -143,7 +143,6 @@ angular.module('umbraco.services') /** Called to update the current user's timeout */ function setUserTimeoutInternal(newTimeout) { - var asNumber = parseFloat(newTimeout); if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) { currentUser.remainingAuthSeconds = newTimeout; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js index 3d6ad8b265..afcd3b27ff 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js @@ -8,7 +8,8 @@ { "value": 0, "name": "Active", "key": "Active", "color": "success" }, { "value": 1, "name": "Disabled", "key": "Disabled", "color": "danger" }, { "value": 2, "name": "Locked out", "key": "LockedOut", "color": "danger" }, - { "value": 3, "name": "Invited", "key": "Invited", "color": "warning" } + { "value": 3, "name": "Invited", "key": "Invited", "color": "warning" }, + { "value": 4, "name": "Inactive", "key": "Inactive", "color": "warning" } ]; angular.forEach(userStates, function (userState) { diff --git a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js index 24318cce02..a10943c17e 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js @@ -15,6 +15,8 @@ function MainController($scope, $location, appState, treeService, notificationsS $scope.touchDevice = appState.getGlobalState("touchDevice"); $scope.editors = []; $scope.overlay = {}; + $scope.drawer = {}; + $scope.search = {}; $scope.removeNotification = function (index) { notificationsService.remove(index); @@ -41,6 +43,10 @@ function MainController($scope, $location, appState, treeService, notificationsS eventsService.emit("app.closeDialogs", event); }; + $scope.closeSearch = function() { + appState.setSearchState("show", false); + }; + var evts = []; //when a user logs out or timesout @@ -114,9 +120,15 @@ function MainController($scope, $location, appState, treeService, notificationsS }; })); + // events for search + evts.push(eventsService.on("appState.searchState.changed", function (e, args) { + if (args.key === "show") { + $scope.search.show = args.value; + } + })); + // events for drawer // manage the help dialog by subscribing to the showHelp appState - $scope.drawer = {}; evts.push(eventsService.on("appState.drawerState.changed", function (e, args) { // set view if (args.key === "view") { @@ -156,6 +168,7 @@ function MainController($scope, $location, appState, treeService, notificationsS $scope.backdrop = args; })); + // event for infinite editors evts.push(eventsService.on("appState.editors.add", function (name, args) { $scope.editors = args.editors; })); diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index 20b5fd8a12..1f6f2c75b8 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -260,6 +260,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar }); if (found) { //set the route param + found.active = true; $scope.selectedLanguage = found; } } @@ -407,6 +408,13 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar // promises.push($scope.treeApi.syncTree({ path: expandedPaths[i], activate: false, forceReload: true })); //} //execute them sequentially + + // set selected language to active + angular.forEach($scope.languages, function(language){ + language.active = false; + }); + language.active = true; + angularHelper.executeSequentialPromises(promises); }); diff --git a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js deleted file mode 100644 index bec3e97543..0000000000 --- a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * @ngdoc controller - * @name Umbraco.SearchController - * @function - * - * @description - * Controls the search functionality in the site - * - */ -function SearchController($scope, searchService, $log, $location, navigationService, $q) { - - $scope.searchTerm = null; - $scope.searchResults = []; - $scope.isSearching = false; - $scope.selectedResult = -1; - - $scope.navigateResults = function (ev) { - //38: up 40: down, 13: enter - - switch (ev.keyCode) { - case 38: - iterateResults(true); - break; - case 40: - iterateResults(false); - break; - case 13: - if ($scope.selectedItem) { - $location.path($scope.selectedItem.editorPath); - navigationService.hideSearch(); - } - break; - } - }; - - var group = undefined; - var groupNames = []; - var groupIndex = -1; - var itemIndex = -1; - $scope.selectedItem = undefined; - - $scope.clearSearch = function () { - $scope.searchTerm = null; - }; - function iterateResults(up) { - //default group - if (!group) { - - for (var g in $scope.groups) { - if ($scope.groups.hasOwnProperty(g)) { - groupNames.push(g); - - } - } - - //Sorting to match the groups order - groupNames.sort(); - - group = $scope.groups[groupNames[0]]; - groupIndex = 0; - } - - if (up) { - if (itemIndex === 0) { - if (groupIndex === 0) { - gotoGroup(Object.keys($scope.groups).length - 1, true); - } else { - gotoGroup(groupIndex - 1, true); - } - } else { - gotoItem(itemIndex - 1); - } - } else { - if (itemIndex < group.results.length - 1) { - gotoItem(itemIndex + 1); - } else { - if (groupIndex === Object.keys($scope.groups).length - 1) { - gotoGroup(0); - } else { - gotoGroup(groupIndex + 1); - } - } - } - } - - function gotoGroup(index, up) { - groupIndex = index; - group = $scope.groups[groupNames[groupIndex]]; - - if (up) { - gotoItem(group.results.length - 1); - } else { - gotoItem(0); - } - } - - function gotoItem(index) { - itemIndex = index; - $scope.selectedItem = group.results[itemIndex]; - } - - //used to cancel any request in progress if another one needs to take it's place - var canceler = null; - - $scope.$watch("searchTerm", _.debounce(function (newVal, oldVal) { - $scope.$apply(function () { - $scope.hasResults = false; - if ($scope.searchTerm) { - if (newVal !== null && newVal !== undefined && newVal !== oldVal) { - - //Resetting for brand new search - group = undefined; - groupNames = []; - groupIndex = -1; - itemIndex = -1; - - $scope.isSearching = true; - navigationService.showSearch(); - $scope.selectedItem = undefined; - - //a canceler exists, so perform the cancelation operation and reset - if (canceler) { - canceler.resolve(); - canceler = $q.defer(); - } - else { - canceler = $q.defer(); - } - - searchService.searchAll({ term: $scope.searchTerm, canceler: canceler }).then(function (result) { - - //result is a dictionary of group Title and it's results - var filtered = {}; - _.each(result, function (value, key) { - if (value.results.length > 0) { - filtered[key] = value; - } - }); - $scope.groups = filtered; - // check if search has results - $scope.hasResults = Object.keys($scope.groups).length > 0; - //set back to null so it can be re-created - canceler = null; - $scope.isSearching = false; - }); - } - } - else { - $scope.isSearching = false; - navigationService.hideSearch(); - $scope.selectedItem = undefined; - } - }); - }, 200)); - -} -//register it -angular.module('umbraco').controller("Umbraco.SearchController", SearchController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index 50e5c21203..3f44638096 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -122,12 +122,25 @@ app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlH $route.reload(); } else { - //check if the location being changed is only the mculture query string, if so, cancel the routing since this is just - //used as a global persistent query string that does not change routes. - + + //check if the location being changed is only due to global/state query strings which means the location change + //isn't actually going to cause a route change. if (navigationService.isRouteChangingNavigation(currentRouteParams, next.params)) { - //continue the route - $route.reload(); + //The location change will cause a route change. We need to ensure that the global/state + //query strings have not been stripped out. If they have, we'll re-add them and re-route. + + var toRetain = navigationService.retainQueryStrings(currentRouteParams, next.params); + if (toRetain) { + $route.updateParams(toRetain); + } + else { + //continue the route + $route.reload(); + } + } + else { + //navigation is not changing but we should update the currentRouteParams to include all current parameters + currentRouteParams = angular.copy(next.params); } } }); diff --git a/src/Umbraco.Web.UI.Client/src/less/application/grid.less b/src/Umbraco.Web.UI.Client/src/less/application/grid.less index 4448bf2192..7ed2abc898 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -124,10 +124,6 @@ body.umb-drawer-is-visible #mainwrapper{ height: 100%; } -#tree .umb-tree { - padding: 0px 0px 20px 0px; -} - #search-results { z-index: 200; } diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index b2f2f99dd5..ac8279e3ab 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -71,8 +71,7 @@ @import "sections.less"; @import "helveticons.less"; @import "main.less"; -@import "tree.less"; -@import "listview.less"; +@import "listview.less"; @import "gridview.less"; @import "footer.less"; @import "dashboards.less"; @@ -84,6 +83,7 @@ @import "components/application/umb-app-content.less"; @import "components/application/umb-tour.less"; @import "components/application/umb-backdrop.less"; +@import "components/application/umb-search.less"; @import "components/application/umb-drawer.less"; @import "components/application/umb-language-picker.less"; @import "components/application/umb-dashboard.less"; @@ -91,6 +91,12 @@ @import "components/html/umb-expansion-panel.less"; @import "components/html/umb-alert.less"; +@import "components/tree/umb-tree.less"; +@import "components/tree/umb-tree-root.less"; +@import "components/tree/umb-actions.less"; +@import "components/tree/umb-tree-item.less"; + + @import "components/editor.less"; @import "components/overlays.less"; @import "components/card.less"; @@ -99,6 +105,7 @@ @import "components/umb-editor-navigation.less"; @import "components/umb-editor-sub-views.less"; @import "components/editor/subheader/umb-editor-sub-header.less"; +@import "components/umb-flatpickr.less"; @import "components/umb-grid-selector.less"; @import "components/umb-child-selector.less"; @import "components/umb-group-builder.less"; @@ -133,6 +140,7 @@ @import "components/umb-querybuilder.less"; @import "components/umb-pagination.less"; @import "components/umb-mini-list-view.less"; +@import "components/umb-multiple-textbox.less"; @import "components/umb-badge.less"; @import "components/umb-nested-content.less"; @import "components/umb-checkmark.less"; @@ -166,6 +174,7 @@ @import "utilities/_spacing.less"; @import "utilities/_text-align.less"; @import "utilities/_width.less"; +@import "utilities/_cursor.less"; //page specific styles @import "pages/document-type-editor.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less index daa6757f44..b1e9671de3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less @@ -227,13 +227,29 @@ input[type="button"] { font-size: 16px; border: none; background: @green; - color: white; + color: @white; font-weight: bold; &:hover { background: @green-d1; } } +// outlined +.btn-outline { + border: 1px solid @gray-8; + background: @white; + color: @black; + padding: 5px 13px; +} + +.btn-outline:hover, +.btn-outline:focus, +.btn-outline:active { + border-color: @gray-7; + background: transparent; + color: @black; +} + // Cross-browser Jank // -------------------------------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index 7410c09580..78c1616bc6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -790,13 +790,13 @@ h4.panel-title { } .smartphone-portrait { - width: 320px; - height: 504px; + width: 360px; + height: 640px; } .smartphone-landscape { - width: 480px; - height: 256px; + width: 640px; + height: 360px; } .border { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less index f7c1c58ad5..a033e8abef 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less @@ -13,23 +13,31 @@ list-style: none; align-items: center; margin: 0; - margin-right: 10px; + margin-right: -10px; } .umb-app-header__action a { - color: @white; - opacity: 0.6; - font-size: 22px; padding-left: 10px; padding-right: 10px; text-decoration: none; display: flex; align-items: center; + height: @appHeaderHeight; } .umb-app-header__action a:hover, .umb-app-header__action a:focus { - opacity: 1; outline: none; } +.umb-app-header__action-icon { + opacity: 0.6; + color: @white; + font-size: 22px; +} + +.umb-app-header__action a:hover .umb-app-header__action-icon, +.umb-app-header__action a:focus .umb-app-header__action-icon { + opacity: 1; +} + diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less index 42c8fceaa0..2a1d5eb6d3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less @@ -9,7 +9,14 @@ } .umb-dashboard__header { + flex: 0 0 @editorHeaderHeight; + background: @white; + border-bottom: 1px solid @gray-9; padding: 20px 0 0 0; + box-sizing: border-box; + display: flex; + justify-content: flex-end; + flex-direction: column; } .umb-dashboard__content { @@ -17,6 +24,12 @@ overflow: auto; } -.umb-dashboard .umb-tabs-nav { +// we need to do some special styling for tabs inside the dashboard header +.umb-dashboard__header .umb-tabs-nav { margin-bottom: 0; + border: none; +} + +.umb-dashboard__header .umb-tabs-nav .umb-tab > a { + padding-bottom: 23px; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less index 5cf9ca21b3..78f631ac0d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less @@ -1,6 +1,10 @@ .umb-language-picker { position: relative; z-index: @zindexDropdown; + // When the language dropdown is present adjust height on the tree root + ~ #tree .umb-tree-root-link { + height: 50px; + } } .umb-language-picker__toggle { @@ -10,7 +14,7 @@ padding: 0 20px; cursor: pointer; border-bottom: 1px solid @gray-9; - height: 50px; + height: @editorHeaderHeight; box-sizing: border-box; } @@ -40,9 +44,13 @@ font-size: 14px; } - .umb-language-picker__dropdown a:hover, .umb-language-picker__dropdown a:focus { background: @gray-10; text-decoration: none; } + +.umb-language-picker__dropdown-item--current { + background-color: @gray-10; + border-left: 2px solid @turquoise; +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less new file mode 100644 index 0000000000..a8fc9c7f8e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less @@ -0,0 +1,110 @@ + +/* + Search wrapper +*/ +.umb-search { + position: fixed; + z-index: @zindexUmbOverlay; + width: 660px; + max-width: 90%; + transform: translate(-50%, 0); + left: 50%; + top: 20%; + border-radius: @baseBorderRadius; + background: @white; + position: fixed; + box-shadow: 0 10px 20px rgba(0,0,0,.12),0 6px 6px rgba(0,0,0,.14); +} + +/* + Search field +*/ + +.umb-search-input-icon { + font-size: 22px; + color: @gray-7; + padding-left: 20px; + display: flex; + align-items: center; + height: 70px; +} + +.umb-search-input.umb-search-input { + width: 100%; + height: 70px; + border: none; + padding: 20px 20px 20px 15px; + border-radius: @baseBorderRadius; + font-size: 22px; + margin-bottom: 0; +} + +.umb-search-input-clear { + background: none; + border: none; + font-size: 12px; + margin-right: 20px; + color: @gray-3; +} + +.umb-search-input-clear.ng-enter { + opacity: 0; + transition: opacity 100ms ease-in-out; +} + +.umb-search-input-clear.ng-enter.ng-enter-active { + opacity: 1; +} + +/* + Search results +*/ +.umb-search-results { + max-height: 50vh; + overflow-y: auto; +} + +.umb-search-group__title { + background: @gray-10; + padding: 3px 20px; +} + +.umb-search-items { + list-style: none; + margin: 0; + padding-top: 4px; + padding-bottom: 4px; +} + +.umb-search-item > a { + padding: 6px 20px; + display: flex; +} + +.umb-search-item > a:hover, +.umb-search-item > a:focus { + background-color: @gray-10; + text-decoration: none; + outline: none; +} + +.umb-search-item > a:focus { + padding-left: 25px; + transition: padding 60ms ease-in-out; +} + +.umb-search-result__icon { + font-size: 18px; + margin-right: 8px; + color: @gray-1; +} + +.umb-search-result__meta { + display: flex; + flex-direction: column; +} + +.umb-search-result__description { + color: @gray-5; + font-size: 13px; +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less index a97c700935..2c33dc8e84 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less @@ -1,13 +1,13 @@ .umb-tour__loader { background: @white; - z-index: 10000; + z-index: @zindexTourModal; position: fixed; height: 5px; } .umb-tour__pulse { position: fixed; - z-index: 10000; + z-index: @zindexTourModal; display: none; background: transparent; box-shadow: 0 0 0 @green inset; @@ -31,7 +31,7 @@ position: fixed; background: @white; border-radius: @baseBorderRadius; - z-index: 10000; + z-index: @zindexTourModal; width: 320px; max-width: 100%; box-sizing: border-box; @@ -100,3 +100,8 @@ font-size: 14px; line-height: 1.6em; } + +// we need to make sure the tour is on top of everything else +.umb-tour-is-visible .umb-backdrop { + z-index: @zindexTourBackdrop; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less index 388d3587c1..e40282cb58 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less @@ -9,15 +9,22 @@ left: auto; } +.umb-button-group__sub-buttons>li>a { + display: flex; +} + .umb-button-group { .umb-button__button { - border-radius: 3px 0px 0px 3px; + border-radius: @baseBorderRadius; } .umb-button-group__toggle { - border-radius: 0px 3px 3px 0; + border-radius: 0px @baseBorderRadius @baseBorderRadius 0; border-left: 1px solid rgba(0,0,0,0.09); + margin-left: -2px; + padding-left: 10px; + padding-right: 10px; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/card.less b/src/Umbraco.Web.UI.Client/src/less/components/card.less index c0aa4fa9b7..e8b8325183 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -96,7 +96,6 @@ font-size: 12px; text-align: center; width: 100px; - height: 105px; box-sizing: border-box; position: relative; } @@ -114,6 +113,7 @@ width: 100%; height: 100%; border-radius: 3px; + padding-bottom: 5px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index 3a39ec9e1c..f045b0adca 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -102,8 +102,14 @@ input.umb-editor-header__name-input { background: @white; border: 1px solid @gray-8; &:hover { - background-color: @gray-10; - border: 1px solid @gray-8; + border-color: @turquoise-d1; + } +} + +input.umb-editor-header__name-input:disabled { + background-color: @gray-10; + &:hover{ + border-color: @gray-8; } } @@ -174,6 +180,11 @@ a.umb-variant-switcher__toggle { outline: none; } +.umb-variant-switcher_item--not-allowed:not(.umb-variant-switcher_item--current) .umb-variant-switcher__name-wrapper:hover { + background-color: @white !important; + cursor: default; +} + .umb-variant-switcher__item:hover .umb-variant-switcher__split-view { display: block; cursor: pointer; @@ -243,15 +254,17 @@ a.umb-variant-switcher__toggle { height: @editorFooterHeight; padding: 10px 20px; background: @white; - // box-shadow: 0 -1px 3px 0 rgba(0, 0, 0, 0.16); border-top: 1px solid @gray-9; z-index: 1; bottom: 0; + display: flex; + align-items: center; } .umb-editor-footer-content { display: flex; align-items: center; + flex: 1 1 auto; } .umb-editor-footer-content__right-side { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less index 26e0e5fee1..10296b58e3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less @@ -4,9 +4,15 @@ background: @gray-10; display: flex; justify-content: space-between; - margin-top: -20px; + margin-top: -10px; position: relative; top: 0; + + &.nested { + margin-top: 0; + margin-bottom: 0; + background: @gray-10; + } } .umb-editor-sub-header.-umb-sticky-bar { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index 3fbe32134b..6538a751f9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -75,7 +75,7 @@ /* ---------- OVERLAY CENTER ---------- */ .umb-overlay.umb-overlay-center { position: absolute; - width: 500px; + width: 600px; height: auto; top: 50%; left: 50%; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less index 5363a8db9b..43f6697eb1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less @@ -10,6 +10,7 @@ .umb-prevalues-multivalues__left { display: flex; flex: 1 1 auto; + overflow: hidden; } .umb-prevalues-multivalues__right { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less new file mode 100644 index 0000000000..f52258333d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less @@ -0,0 +1,98 @@ +// Tree context menu +// ------------------------- +.umb-actions { + margin: 0; + list-style: none; + user-select: none; + + .sep { + display: block; + border-top: 1px solid @gray-9; + + &:first-child { + border-top: none; + } + } + + .menu-label { + display: inline-block; + vertical-align: middle; + padding-left: 15px; + } + + .icon { + font-size: 18px; + vertical-align: middle; + color: @gray-3; + } +} + +.umb-action-link { + white-space: nowrap; + font-size: 15px; + color: @black; + padding: 9px 25px 9px 20px; + text-decoration: none; + cursor: pointer; + display: flex; + align-items: center; + + body.touch & { + padding: 7px 25px 7px 20px; + font-size: 110%; + } +} + +.umb-action-link:hover, +.umb-action-link:focus, +.umb-action.selected { + color: @black !important; + background: @gray-10 !important; + text-decoration: none; +} + +.umb-actions-child { + + .umb-action { + display: block; + + &.add { + margin-top: 20px; + border-top: 1px solid @gray-8; + padding-top: 20px; + + i { + opacity: 0.4; + } + } + } + + .umb-action-link { + clear: both; + padding-left: 10px; + } + + .icon { + font-size: 30px; + min-width: 30px; + text-align: center; + line-height: 24px; + /* set line-height to ensure all icons use same line-height */ + } + + .menu-label { + font-size: 14px; + color: @black; + margin-left: 10px; + } + + small { + font-size: 12px; + display: block; + clear: right; + line-height: 14px; + color: @gray-6; + white-space: normal; + margin-top: 2px; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less new file mode 100644 index 0000000000..002d076461 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less @@ -0,0 +1,72 @@ +.umb-tree-item { + display: block; + min-width: 100%; + width: auto; + + &:hover ins { + visibility: visible; + cursor: pointer + } +} + +.umb-tree-item > .umb-tree-item__inner { + + &:hover .umb-tree-item__label { + overflow: hidden; + margin-right: 6px; + } + + // Loading Animation + // ------------------------ + .l { + width: 100%; + height: 1px; + overflow: hidden; + position: absolute; + left: 0; + bottom: 0; + + div { + .umb-loader; + } + } + + .umb-tree-item__label { + padding: 7px 0 5px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1 0 auto; + } +} + +.umb-tree-item.current > .umb-tree-item__inner { + + background: @turquoise-d1; + + // override small icon color. TODO => check usage +// &:before { +// color: @turquoise-l2; +// } + + .umb-options { + + &:hover i { + opacity: .7; + } + + i { + background: @white; + border-color: @turquoise-d1; + transition: opacity 120ms ease; + } + } + + a, + .umb-tree-icon, + ins { + color: @white !important; + background-color: @turquoise-d1; + border-color: @turquoise-d1; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-root.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-root.less new file mode 100644 index 0000000000..72a008b8b6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-root.less @@ -0,0 +1,23 @@ +.umb-tree-root { + + &-link { + display: flex; + align-items: center; + width: 100%; + padding-left: 20px; + color: @gray-2; + height: @editorHeaderHeight; + } + + h5, + h6 { + margin: 0; + width: 100%; + display: flex; + color: @gray-2; + } + + .umb-options { + align-self: center; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less new file mode 100644 index 0000000000..4705b8cc3e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -0,0 +1,288 @@ +// Tree +// ------------------------- +.umb-tree { + margin: 0; + min-width: 100%; + width: auto; + padding: 0 0 20px; + list-style-type: none; + + * { + white-space: nowrap; + } + + a, a:hover { + outline: none; + text-decoration: none; + + // TODO => confirm not in use +// &.noSpr { +// background-position: 0 +// } + } + + ins { + margin: -4px 0 0 -16px; + width: 16px; + height: 16px; + visibility: hidden; + text-decoration: none; + font-size: 12px; + transition: opacity 120ms ease; + + &:hover { + opacity: .7; + } + } + + i.noSpr { + display: inline-block; + margin-top: 1px; + width: 16px; + height: 16px; + line-height: 16px; + } + + ul { + padding: 0; + margin: 0; + min-width: 100%; + width: 100%; + + &.collapsed { + display: none; + } + } + + //loader defaults + .umb-loader { + height: 10px; + margin: 10px 10px 10px 10px; + } + + .search-subtitle { + color: @gray-7; + display: block; + padding-left: 35px; + } +} + +body.touch .umb-tree { + ins { + font-size: 14px; + visibility: visible; + padding: 7px; + } + + .umb-tree-item > .umb-tree-item__inner { + padding-top: 8px; + padding-bottom: 8px; + font-size: 110%; + } + + // change height of this if touch devices should have a different height of preloader. + .umb-tree-item .l div { + padding: 0; + } +} + +.umb-tree-root, .umb-tree-item__inner { + padding: 0; + position: relative; + overflow: hidden; + display: flex; + flex-wrap: nowrap; + align-items: center; + + &.active { + background: @gray-10; + } + + &:hover { + background: @gray-10; + + > .umb-options { + visibility: visible; + } + } +} + +.umb-tree-header { + display: flex; + padding: 20px 0 20px 20px; + box-sizing: border-box; + color: @gray-2; + font-weight: bold; + font-size: 15px; +} + +.umb-tree-icon, +.umb-tree-node-search { + cursor: pointer; +} + +.umb-tree .umb-search-group { + position: inherit; + display: inherit; + + h6 { + padding: 10px 0 10px 20px; + font-weight: inherit; + background: @gray-10; + font-size: 14px; + } + + &:hover { + background: inherit; + } + + &-item { + padding-left: 20px; + } + + &-link { + display: flex; + flex-wrap: wrap; + flex-direction: column; + font-weight: normal !important; + } +} + +.umb-tree .umb-tree-node-checked i[class^="icon-"], +.umb-tree .umb-tree-node-checked i[class*=" icon-"] { + font-family: 'icomoon' !important; + color: @green !important; + + &::before { + content: "\e165" !important; + font-family: inherit; + } +} + +.umb-options { + visibility: hidden; + display: flex; + flex: 0 0 auto; + justify-content: flex-end; + padding: 9px 5px; + text-align: center; + margin: 0 10px 0 auto; + cursor: pointer; + border-radius: @baseBorderRadius; + + &:hover { + background: @btnBackgroundHighlight; + } + + i { + height: 5px !important; + width: 5px !important; + border-radius: 20px; + background: @black; + display: inline-block; + margin: 0 2px 0 0; + + &:last-child { + margin: 0; + } + } + + .hide-options & { + display: none !important; + } +} + + +// Tree item states +// ------------------------- +.not-published { + > i.icon, + a { + opacity: 0.6; + } +} + +.not-allowed { + > i.icon, + a { + cursor: not-allowed; + } +} + +.protected, +.has-unpublished-version, +.is-container, +.locked { + &::before { + font-family: 'icomoon'; + position: absolute; + font-size: 20px; + padding-left: 7px; + padding-top: 7px; + bottom: 0; + } +} + +.protected::before { + content: "\e256"; + color: @red; +} + +.has-unpublished-version::before { + content: "\e25a"; + color: @green; +} + +.is-container::before { + content: "\e04e"; + color: @turquoise; + font-size: 8px; + padding-left: 13px; + padding-top: 8px; + pointer-events: none; +} + + +.locked::before { + content: "\e0a7"; + color: @red; +} + +.no-access { + .umb-tree-icon, + .root-link, + .umb-tree-item__label { + color: @gray-7; + cursor: not-allowed; + } +} + + +// Tree icons +// ------------------------- +.umb-tree-icon { + vertical-align: middle; + margin: 0 13px 0 0; + color: @gray-1; + font-size: 20px; + + &.blue { + color: @blue; + } + + &.green { + color: @green; + } + + &.purple { + color: @purple; + } + + &.orange { + color: @orange; + } + + &.red { + color: @red; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less index c7905879d8..a987c5daa3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less @@ -1,18 +1,22 @@ .umb-color-swatches { + display: flex; + flex-flow: row wrap; .umb-color-box { border: none; color: white; cursor: pointer; - padding: 5px; + padding: 1px; text-align: center; text-decoration: none; - display: inline-block; margin: 5px; border-radius: 3px; width: 30px; height: 30px; transition: box-shadow .3s; + display: flex; + align-items: center; + justify-content: center; &:hover, &:focus { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); @@ -28,4 +32,55 @@ } } } + + &.with-labels { + + .umb-color-box { + width: 120px; + height: 100%; + display: flex; + flex-flow: row wrap; + + .umb-color-box-inner { + display: flex; + flex-flow: column wrap; + flex: 0 0 100%; + max-width: 100%; + min-height: 80px; + padding-top: 10px; + + .umb-color-box__label { + background: #fff; + font-size: 14px; + display: flex; + flex-flow: column wrap; + flex: 0 0 100%; + padding: 1px 5px; + max-width: 100%; + margin-top: auto; + margin-bottom: -3px; + margin-left: -1px; + margin-right: -1px; + text-indent: 0; + text-align: left; + border: 1px solid @gray-8; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + overflow: hidden; + + .umb-color-box__name { + color: @black; + font-weight: bold; + margin-top: 3px; + } + + .umb-color-box__description { + font-size: 12px; + line-height: 1.5em; + color: @gray-3; + } + } + } + } + } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less index 15a319b520..b0c90483f7 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less @@ -36,7 +36,7 @@ } .umb-content-grid__icon.-light { - color: @gray-8; + color: @gray-5; } @@ -58,7 +58,7 @@ } .umb-content-grid__item-name.-light { - color: @gray-8; + color: @gray-5; } .umb-content-grid__details-list { @@ -69,7 +69,7 @@ } .umb-content-grid__details-list.-light { - color: @gray-8; + color: @gray-5; } .umb-content-grid__details-label { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less index 07bf8955aa..4803c05f6e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less @@ -8,6 +8,7 @@ width: auto; padding: 50px 0; border: 1px dashed @gray-8; + background-color: @white; text-align: center; color: @gray-3; margin: 0 0 20px 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-flatpickr.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-flatpickr.less new file mode 100644 index 0000000000..1848d55d96 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-flatpickr.less @@ -0,0 +1,21 @@ +.flatpickr-calendar.flatpickr-calendar { + border-radius: @baseBorderRadius; + box-shadow: 0 5px 10px 0 rgba(0,0,0,0.16); +} + +span.flatpickr-day { + border-radius: @baseBorderRadius; + border: none; +} + +span.flatpickr-day:hover { + background-color: @gray-10; +} + +span.flatpickr-day.selected { + background-color: @turquoise; +} + +span.flatpickr-day.selected:hover { + background-color: @turquoise-d1; +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less index f4fecf89fe..d91f24cbca 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less @@ -7,7 +7,7 @@ } .umb-folder-grid__folder { - background: @gray-10; + background: @white; margin: 5px; display: flex; align-items: center; @@ -20,6 +20,8 @@ justify-content: space-between; cursor: pointer; user-select: none; + box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); + border-radius: 3px; } .umb-folder-grid__folder:focus, @@ -29,7 +31,8 @@ .umb-folder-grid__folder:hover { text-decoration: none; - background-color: @gray-9; + box-shadow: 0 3px 6px 0 rgba(0,0,0,0.16); + transition: box-shadow 150ms ease-in-out; } .umb-folder-grid__folder-description { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index 2eb2735279..be6729bf56 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -8,13 +8,12 @@ .umb-group-builder__group { min-height: 86px; - margin: 50px 0 0 0; - border: 2px solid @gray-7; - border-radius: 0 10px 10px 10px; - padding: 10px 10px 5px 10px; + border: 1px solid transparent; + border-radius: @baseBorderRadius; box-sizing: border-box; background-color: @white; - position:relative; + position: relative; + box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); } .umb-group-builder__group.-active { @@ -24,6 +23,7 @@ .umb-group-builder__group.-inherited { border-color: @gray-9; animation: fadeIn 0.5s; + box-shadow: none; } .umb-group-builder__group.-placeholder { @@ -32,10 +32,11 @@ justify-content: center; align-items: center; cursor: pointer; - border: 1px dashed @gray-8; + border: 1px dashed @gray-7; color: @turquoise-d1; font-weight: bold; position: relative; + box-shadow: none; } .umb-group-builder__group.-sortable { @@ -57,9 +58,8 @@ } .umb-group-builder__group-remove { - position: absolute; - top: -30px; - right: 20px; + position: relative; + margin-left: auto; font-size: 18px; } @@ -71,28 +71,15 @@ .umb-group-builder__group-title-wrapper { display: flex; align-items: center; - margin-left: -12px; - margin-top: -55px; -} - -.umb-group-builder__group-title-wrapper.-placeholder { - position: absolute; - left: -1px; - top: -44px; - margin-left: 0; - margin-top: 0; + border-bottom: 1px solid @gray-9; + padding: 10px 15px; } .umb-group-builder__group-title { - padding: 5px 9px 0 9px; - height: 38px; - background: @white; - border: 2px solid @gray-7; - border-bottom: none; - border-radius: 10px 10px 0 0; font-weight: bold; display: flex; align-items: center; + color: @black; } .umb-group-builder__group-title-icon { @@ -117,10 +104,14 @@ input.umb-group-builder__group-title-input { border-color: transparent; background: transparent; font-weight: bold; - color: @gray-3; + color: @black; margin-bottom: 0; } +input.umb-group-builder__group-title-input:disabled:hover { + border: none; +} + .umb-group-builder__group-title-input:hover { border-color: @inputBorder; } @@ -131,17 +122,14 @@ input.umb-group-builder__group-title-input { .umb-group-builder__group-inherited-label { font-size: 13px; - color: @gray-8; display: inline-block; position: relative; top: 2px; - margin-left: 10px; } .umb-group-builder__group-sort-order { - display: flex; - align-items: center; - margin-left: 10px; + margin-right: 10px; + margin-left: auto; } /* ---------- PROPERTIES ---------- */ @@ -149,7 +137,7 @@ input.umb-group-builder__group-title-input { .umb-group-builder__properties { list-style: none; margin: 0; - padding: 0; + padding: 15px; min-height: 35px; // the height of a sortable property } @@ -157,35 +145,23 @@ input.umb-group-builder__group-title-input { position: relative; display: flex; flex-flow: row; - margin-bottom: 5px; - border: 1px solid transparent; - border-radius: 5px; - padding: 5px; box-sizing: border-box; + border-bottom: 1px solid @gray-10; + padding: 10px 0; } -.umb-group-builder__property:hover { - border: 1px dashed @inputBorder; +.umb-group-builder__property:first-of-type { + padding-top: 0; } -.umb-group-builder__property.-placeholder { - background: @white; - border: 1px dashed @gray-8; - border-radius: 5px; - cursor: pointer; - align-items: center; - animation: fadeIn 0.5s; +.umb-group-builder__property:last-of-type { + margin-bottom: 15px; } .umb-group-builder__property.-inherited { - border: transparent; animation: fadeIn 0.5s; } -.umb-group-builder__property.-inherited:hover { - border: transparent; -} - .umb-group-builder__property.-locked { border: transparent; animation: fadeIn 0.5s; @@ -198,14 +174,16 @@ input.umb-group-builder__group-title-input { .umb-group-builder__property.-sortable, .umb-group-builder__property.-sortable-locked { min-height: 35px; - border-radius: 5px; + border-radius: @baseBorderRadius; border: none; animation: none; align-items: center; + padding: 5px 10px; + margin-bottom: 5px; } .umb-group-builder__property.-sortable { - background: @gray-9; + background: @gray-10; color: @gray-1; cursor: move; } @@ -217,16 +195,17 @@ input.umb-group-builder__group-title-input { .umb-group-builder__property-meta { - flex: 0 0 175px; + flex: 0 0 250px; margin-right: 20px; } .umb-group-builder__property-meta.-full-width { flex: 1; + margin-right: 0; } .umb-group-builder__property-meta-alias { - font-size: 10px; + font-size: 12px; color: @gray-3; word-break: break-word; line-height: 1.5; @@ -273,7 +252,7 @@ input.umb-group-builder__group-title-input { overflow: hidden; position: relative; padding: 35px 10px 25px 10px; - background: url("../img/checkered-background-20.png"); + background-color: @gray-10; } .umb-group-builder__property-preview:hover { @@ -357,42 +336,6 @@ input.umb-group-builder__group-title-input { margin-right: 3px; } - -/* ---------- PLACEHOLDER BOX ---------- */ - -.umb-group-builder__placeholder-box { - background: @gray-9; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - font-size: 13px; - font-weight: bold; - color: @turquoise-d1; -} - -.umb-group-builder__placeholder-box.-input { - height: 10px; - margin-bottom: 5px; -} - -.umb-group-builder__placeholder-box.-input-small { - height: 5px; - margin-bottom: 5px; - width: 25%; -} - -.umb-group-builder__placeholder-box.-text-full-width { - height: 3px; - margin-bottom: 3px; -} - -.umb-group-builder__placeholder-box.-text-short { - height: 3px; - margin-bottom: 3px; - width: 75%; -} - /* ---------- SORTABLE ---------- */ .umb-group-builder__group-sortable-placeholder { @@ -418,6 +361,11 @@ input.umb-group-builder__group-title-input { text-align: center; } +input.umb-group-builder__group-sort-value { + margin-bottom: 0; + margin-left: auto; +} + /* ---------- DIALOGS ---------- */ diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less index 6ca57e0829..6b03d80214 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less @@ -27,12 +27,18 @@ } .umb-iconpicker-item a:hover, -.umb-iconpicker-item a:focus, -.umb-iconpicker-item.-selected { +.umb-iconpicker-item a:focus { background: @gray-10; outline: none; } +.umb-iconpicker-item.-selected { + background: @gray-10; + border: solid 1px @turquoise-d1; + border-radius: @baseBorderRadius; + box-sizing: border-box; +} + .umb-iconpicker-item a:active { background: @gray-10; } @@ -54,11 +60,6 @@ border-radius: 3px; } - // Hide Circle when not active - i.small{ - display: none; - } - // Circle behind the checkmark i.small.active{ font-size: 14px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less index 8d3a880105..3356c1de68 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less @@ -30,7 +30,19 @@ } .umb-media-grid__item.-file { - background-color: @gray-10; + background-color: @white; +} + +.umb-media-grid__item-file-icon > span { + color: @white; + background: @gray-4; + padding: 1px 3px; + font-size: 10px; + line-height: 130%; + display: block; + margin-top: -30px; + margin-left: -10px; + position: relative; } .umb-media-grid__item.-selected { @@ -87,7 +99,7 @@ .umb-media-grid__item.-file .umb-media-grid__item-overlay { opacity: 1; color: @gray-4; - background: @gray-10; + background: @white; } .umb-media-grid__item.-file:hover .umb-media-grid__item-overlay, diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less new file mode 100644 index 0000000000..21f59a3e2d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less @@ -0,0 +1,34 @@ +.umb-multiple-textbox .textbox-wrapper { + align-items: center; + margin-bottom: 15px; +} + +.umb-multiple-textbox .textbox-wrapper .umb-editor { + margin-bottom: 0; +} + +.umb-multiple-textbox .textbox-wrapper i { + margin-right: 5px; +} + +.umb-multiple-textbox .textbox-wrapper i.handle { + margin-left: 5px; +} + +.umb-multiple-textbox .textbox-wrapper a.remove { + margin-left: 5px; + text-decoration: none; +} + +.umb-multiple-textbox .add-link { + &:extend(.umb-node-preview-add); +} + +.umb-editor-wrapper .umb-multiple-textbox .add-link { + &:extend(.umb-editor-wrapper .umb-node-preview); +} + +.umb-modal .umb-multiple-textbox .textbox-wrapper .umb-editor { + flex: 1 1 auto; + width: auto; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index d1ad8ee8ff..514a73407c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -212,3 +212,14 @@ position: relative; transform: translate(-50%, -25%); } + + +// this resolves the layout issue introduced in nested content in 7.12 with the addition of the input for link anchors +// the attribute selector ensures the change only applies to the linkpicker overlay +.form-horizontal .umb-nested-content--narrow [ng-controller*="Umbraco.Overlays.LinkPickerController"] .controls-row { + margin-left:0!important; + + .umb-textarea, .umb-textstring { + width:100%; + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index b302ed2654..b729aa92bc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -166,7 +166,7 @@ input.umb-table__input { } -.-content .-unpublished { +.-content :not(.with-unpublished-version).-unpublished { .umb-table__name > * { opacity: .4; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less index d3c556bc50..9da54f0bf9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less @@ -3,7 +3,7 @@ margin-bottom: 0; list-style: none; border-bottom: 1px solid @gray-9; - display: inline-block; + display: block; margin-bottom: 20px; } @@ -17,7 +17,7 @@ cursor: pointer; border-bottom: 2px solid transparent; color: @gray-3; - padding: 5px 20px 10px 20px; + padding: 5px 20px 15px 20px; transition: color 150ms ease-in-out; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-permission.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-permission.less index c041f3f4ea..f5a81e3393 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-permission.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-permission.less @@ -27,5 +27,5 @@ .umb-permission__description { font-size: 13px; - color: @gray-5; + color: @gray-4; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less index e79e474935..7acf47d22e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less @@ -68,8 +68,8 @@ position: relative; padding: 15px; flex: 1 1 auto; - background-color: @gray-10; - border: 1px solid @gray-9; + background-color: @white; + box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); border-radius: 3px; box-sizing: border-box; display: flex; diff --git a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less index 7a96c906e3..d40b59cf81 100644 --- a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less +++ b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less @@ -8,7 +8,6 @@ .umb-healthcheck-help-text { line-height: 1.6em; - margin-bottom: 30px; max-width: 750px; } @@ -37,6 +36,7 @@ .umb-healthcheck-group:hover { box-shadow: 0 3px 6px 0 rgba(0,0,0,0.16); cursor: pointer; + transition: box-shadow 150ms ease-in-out; } .umb-healthcheck-group__load-container { diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index e60a2a0c87..2271703540 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -148,6 +148,10 @@ /* TEMP */ +.umb-minilistview { + .umb-table-row.not-allowed { opacity: 0.6; cursor: not-allowed; } +} + .umb-listview .table-striped tbody td { position: relative } diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index d869d1d9af..0284a79865 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -48,6 +48,7 @@ h5.-black { border: none } .bootstrap-datetimepicker-widget { + width: auto !important; td { &.active, span.active { background: @turquoise !important; @@ -116,7 +117,7 @@ h5.-black { /* FORM GRID */ .umb-pane { - margin: 30px 20px; + margin: 20px; } .umb-control-group { border-bottom: 1px solid @gray-10; @@ -650,3 +651,12 @@ input[type=checkbox]:checked + .input-label--small { .bootstrap-datetimepicker-widget td span { border-radius: 0 !important; } + +.diff del { + background-color: @red-l3; +} + +.diff ins { + background-color: @green-l3; + text-decoration: none; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 5a85c19124..8ef2c8c859 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -75,6 +75,7 @@ bottom: 0px; position: absolute; padding: 0px; + background: #fff; } .umb-dialog .umb-btn-toolbar .umb-control-group{ diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index c894784dec..4e7937830c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -5,7 +5,7 @@ width: 100%; height: 100%; position: absolute; - z-index: 10000; + z-index: 65537; top: 0; left: 0; margin: 0 !important; @@ -58,12 +58,13 @@ .login-overlay .form { background: @white; - padding: 25px; + padding: 30px; width: 500px; margin-left: 25px; margin-right: 25px; margin-top: auto; margin-bottom: auto; + border-radius: @baseBorderRadius; } .login-overlay .form input[type="text"], diff --git a/src/Umbraco.Web.UI.Client/src/less/properties.less b/src/Umbraco.Web.UI.Client/src/less/properties.less index c7d156d44c..916f1b5a3a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/properties.less +++ b/src/Umbraco.Web.UI.Client/src/less/properties.less @@ -1,61 +1,19 @@ //----- SCHEDULED PUBLISH ------ -.place-holder { - height: 60px; - width: 60px; - margin: 15px auto; - background-color: @gray-8; -} - .date-wrapper { display: flex; - justify-content: space-around; flex-direction: row; + border-top: 1px solid @gray-10; + border-bottom: 1px solid @gray-10; } -.date-container { - text-align: center; +.date-wrapper__date { + padding: 10px; + flex: 1 1 50%; } -.date-wrapper__number{ - font-size: 40px; - line-height: 50px; - color: @gray-2; - font-weight: 900; -} - -.date-container__title { - font-size: 16px; - font-weight: bold; - color: @gray-3; - margin-bottom: 5px; -} - -.date-container__date { - padding: 0 10px; -} -.date-container__date:hover { - background-color: @gray-10; - cursor: pointer; -} - -.date-wrapper__date{ - font-size: 13px; - color: @gray-6; - margin: 0; -} - -.data-wrapper__add{ - font-size: 18px; - line-height: 10px; - color: @gray-8; - font-weight: 900; - margin: 0; -} - -.date-separate { - width: 1px; - background-color: @gray-8; +.date-wrapper__date:last-of-type { + border-left: 1px solid @gray-10; } //------------------- HISTORY ------------------ @@ -95,6 +53,10 @@ font-size: 14px; } +.history-item__badge { + margin-right: 5px; +} + /* RESPONSIVE */ @media (min-width: 1101px) and (max-width: 1365px), (max-width: 979px) { diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 2b716b9b9f..43f5aa5a9a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -133,27 +133,6 @@ div.umb-codeeditor .umb-btn-toolbar { // // Color picker // -------------------------------------------------- -ul.color-picker li { - padding: 2px; - margin: 3px; - border: 2px solid transparent; - width: 60px; - - .thumbnail{ - min-width: auto; - width: inherit; - padding: 0; - } - - a { - height: 50px; - display:flex; - align-items: center; - justify-content: center; - cursor:pointer; - margin: 0 0 5px; - } -} /* pre-value editor */ .control-group.color-picker-preval { @@ -174,7 +153,7 @@ ul.color-picker li { div.color-picker-prediv { display: inline-flex; align-items: center; - max-width: 80%; + max-width: 85%; pre { display: inline-flex; @@ -357,13 +336,30 @@ ul.color-picker li { text-align: center; } -.umb-sortable-thumbnails .umb-icon-holder .icon{ +.umb-sortable-thumbnails .umb-icon-holder .icon { font-size: 40px; line-height: 50px; color: @gray-3; display: block; } +.umb-sortable-thumbnails .umb-icon-holder .file-icon > span { + color: @white; + background: @gray-4; + padding: 1px 3px; + font-size: 10px; + line-height: 130%; + display: block; + margin-top: -30px; + width: 2em; +} + +.umb-sortable-thumbnails .umb-icon-holder .file-icon + small { + display: block; + margin-top: 1em; +} + + .umb-sortable-thumbnails .umb-sortable-thumbnails__wrapper { width: 124px; height: 124px; diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less deleted file mode 100644 index cf163cd72d..0000000000 --- a/src/Umbraco.Web.UI.Client/src/less/tree.less +++ /dev/null @@ -1,553 +0,0 @@ -// item-list -// ------------------------- - - -.umb-item-list { - margin: 0; - width: auto; - display: block -} -.umb-item-list li { - display: block; - width: auto; -} - - - - -// Tree -// ------------------------- - -.umb-tree { - margin: 0; - min-width: 100%; - width: auto; -} - -.umb-tree li { - display: block; - min-width: 100%; - width: auto; -} -.umb-tree li.current > div, -.umb-tree div.selected { - background: @turquoise-d1; -} -.umb-tree li.current > div a.umb-options i, -.umb-tree div.selected i { - background: @white; - border-color: @turquoise-d1; - transition: opacity 120ms ease; -} - -.umb-tree li.current > div a.umb-options:hover i, -.umb-tree div.selected i { - opacity: .7; -} - -.umb-tree li.current > div a, -.umb-tree li.current > div i.icon, -.umb-tree li.current > div ins { - color: @white !important; - background-color: @turquoise-d1; - border-color: @turquoise-d1; -} - -.umb-tree li.root > div:first-child { - padding: 0; -} - -.umb-tree li.root > div h5, .umb-tree li.root > div h6 { - margin: 0; - width: 100%; - display: flex; - align-items: center; -} - -.umb-tree li.root > div:first-child h5 > a, .umb-tree-header { - display: flex; - padding: 20px 0 20px 20px; - box-sizing: border-box; -} - -.umb-tree * { - white-space: nowrap -} -.umb-tree ul { - padding: 0; - margin: 0; - min-width: 100%; - width: 100%; - //display: table -} - -.umb-tree ul.collapsed { - display:none; -} - -.umb-tree a { - cursor:pointer; - text-decoration: none; - outline: none; -} - -.umb-tree a:hover { - text-decoration: none -} - -/*.umb-tree div.tree-node { - padding: 5px 0 5px 0; - position: relative; - overflow: hidden; - display: flex; - flex-wrap: nowrap; - align-items: center; -}*/ - -.umb-tree div { - padding: 5px 0 5px 0; - position: relative; - overflow: hidden; - display: flex; - flex-wrap: nowrap; - align-items: center; -} - -.umb-tree a.noSpr { - background-position: 0 -} - -.umb-tree div > a.umb-options { - visibility: hidden; - flex: 0 0 auto; - margin-left: auto; -} - -.umb-tree div:hover > a.umb-options { - visibility: visible; -} - -.umb-tree li.root > div a, -.umb-tree li.root h5, .umb-tree-header { - color: @gray-2; - font-weight: bold; - font-size: 15px; -} - -.umb-tree ins { - margin: -4px 0 0 -16px; - width: 16px; - height: 16px; - visibility: hidden; - text-decoration: none; - font-size: 12px; - transition: opacity 120ms ease; -} - -.umb-tree ins:hover { - opacity: .7; -} - -.umb-tree li:hover ins { - visibility: visible; - cursor: pointer -} - -.umb-tree li div { - padding: 0; -} - -.umb-tree li > div a:not(.umb-options) { - padding: 6px 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - flex: 1 0 auto; -} - -.umb-tree li > div:hover a:not(.umb-options) { - overflow: hidden; - margin-right: 6px; -} - -.umb-tree .icon { - vertical-align: middle; - margin: 0 13px 0 0; - color: @gray-1; - font-size: 20px; -} - -.umb-tree-icon { - cursor: pointer; -} - -.umb-tree i.noSpr { - display: inline-block; - margin-top: 1px; - width: 16px; - height: 16px; - line-height: 16px; -} - -.umb-tree div:hover { - background: @gray-10; -} - -.umb-tree small.search-subtitle{ - color: @gray-7; - display: block; - padding-left: 35px; -} - -.umb-tree .umb-tree-node-search { - cursor:pointer; - /*color:@turquoise;*/ -} - -.umb-tree div.umb-search-group { - position: inherit; - display: inherit; -} - -.umb-tree div.umb-search-group:hover { - background: inherit; -} -.umb-tree div.umb-search-group h6 { - /*color: @gray-5;*/ - padding: 10px 0 10px 20px; - font-weight: inherit; - background: @gray-10; - font-size: 14px; - font-weight: bold; -} - -.umb-tree .umb-search-group-item { - padding-left: 20px; -} - -.umb-tree .umb-search-group-item-link { - display: flex; - flex-wrap: wrap; - flex-direction: column; - font-weight: normal !important; -} - -.icon-check:before { - content: "\e165"; -} - -.umb-tree .umb-tree-node-checked i[class^="icon-"], -.umb-tree .umb-tree-node-checked i[class*=" icon-"] { - font-family: 'icomoon' !important; - color:@green !important; -} -.umb-tree .umb-tree-node-checked i:before { - /*check box*/ - content: "\e165" !important; - font-family: inherit; -} - -a.umb-options { - visibility: hidden; - display: flex; - justify-content: flex-end; - padding: 9px 5px; - text-align: center; - cursor: pointer; - margin-right: 10px; -} - -a.umb-options i { - height: 5px !important; - width: 5px !important; - border-radius: 20px; - background: @black; - display: inline-block; - margin: 0 2px 0 0; -} - -a.umb-options i:last-child { - margin: 0; -} - -a.umb-options:hover { - background: @btnBackgroundHighlight; - .border-radius(@baseBorderRadius); -} - -li.root > div > a.umb-options { - top: 18px; - display: flex; - padding: 10px 5px; -} - -.hide-options a.umb-options{display: none !important} -.hide-header h5{display: none !important} - - -.umb-icon-item { - padding: 2px; - padding-left: 55px; - display: block; - position: relative; -} - -.umb-icon-item:hover { - background: @gray-10; -} -.umb-icon-item i.icon { - position: absolute; - top: 8px; - left: 19px; -} -.umb-icon-item a:hover div { - text-decoration: underline; -} - -.umb-icon-item a { - color: @gray-3; - padding-top: 3px; - height: 15px; - font-size: 12px; - text-decoration: none; -} -.umb-icon-item small { - color: @gray-6; - font-size: 10px; - display: block -} -.umb-icon-item:hover a.umb-options { - visibility: visible -} -.umb-icon-item .umb-spr { - float: left -} - - - -// Tree item states -// ------------------------- -div.not-published > i.icon,div.not-published > a{ - opacity: 0.6; -} -div.protected:before{ - content:"\e256"; - font-family: 'icomoon'; - color: @red; - position: absolute; - font-size: 20px; - padding-left: 7px; - padding-top: 7px; - bottom: 0; -} - -div.has-unpublished-version:before{ - content:"\e25a"; - font-family: 'icomoon'; - color: @green; - position: absolute; - font-size: 20px; - padding-left: 7px; - padding-top: 7px; - bottom: 0; -} - -div.not-allowed > i.icon,div.not-allowed > a{ - cursor: not-allowed; -} - -// override small icon color -.umb-tree li.current > div:before { - color: @turquoise-l2; -} -div.is-container:before{ - content:"\e04e"; - font-family: 'icomoon'; - color: @turquoise; - position: absolute; - font-size: 8px; - padding-left: 13px; - padding-top: 8px; - pointer-events: none; - bottom: 0; -} - -div.locked:before{ - content:"\e0a7"; - font-family: 'icomoon'; - color: @red; - position: absolute; - font-size: 20px; - padding-left: 7px; - padding-top: 7px; - bottom: 0; -} - -.umb-tree li div.no-access .umb-tree-icon, -.umb-tree li div.no-access .root-link, -.umb-tree li div.no-access .umb-tree-item__label { - color: @gray-7; - cursor: not-allowed; -} - -// Tree context menu -// ------------------------- -.umb-actions { - margin: 0; - padding: 0px; - list-style: none; - user-select: none; -} - -.umb-actions li.sep { - display: block; - border-top: 1px solid @gray-9; -} - -.umb-actions li.sep:first-child { - border-top: none; -} - -.umb-actions a { - white-space: nowrap; - display: block; - font-size: 15px; - color: @black; - padding: 9px 25px 9px 20px; - text-decoration: none; - cursor: pointer; - display: flex; - align-items: center; -} - -.umb-actions a:hover, .umb-actions a:focus, -.umb-actions li.selected { - color: @black !important; - background: @gray-10 !important; -} - -.umb-actions .menu-label { - display: inline-block; - vertical-align: middle; - padding-left: 15px; -} - -.umb-actions i { - color: @gray-6; - font-size: 18px; - vertical-align: middle; - color: @gray-3; -} - -.umb-actions-child { - list-style: none; - display: block; - margin: 0px; -} - -.umb-actions-child li { - display: block; -} - -.umb-actions-child a { - display: block; - clear: both; - text-decoration: none; - padding-left: 10px; -} -.umb-actions-child li .menu-label { - font-size: 14px; - color: @black; - margin-left: 10px; -} - -.umb-actions-child li .menu-label small { - font-size: 12px; - display: block; - clear: right; - line-height: 14px; - color: @gray-6; - white-space: normal; - margin-top: 2px; -} -.umb-actions-child li a:hover .menuLabel small { - text-decoration: none !important -} -.umb-actions-child i { - font-size: 30px; - min-width: 30px; - text-align: center; - line-height: 24px; /* set line-height to ensure all icons use same line-height */ -} - -.umb-actions-child li.add { - margin-top: 20px; - border-top: 1px solid @gray-8; - padding-top: 20px; -} -.umb-actions-child li.add i { - opacity: 0.4; -} - - -// Tree icon colors -// ------------------------- - -.umb-tree i.icon.blue { - color: @blue; -} -.umb-tree i.icon.green { - color: @green; -} -.umb-tree i.icon.purple { - color: @purple; -} -.umb-tree i.icon.orange { - color: @orange; -} -.umb-tree i.icon.red { - color: @red; -} - - - - -// Loading Animation -// ------------------------ - -.umb-tree li div.l{ - width:100%; - height:1px; - overflow:hidden; - position: absolute; - left: 0; - bottom: 0; -} - -.umb-tree li div.l div { - .umb-loader; -} - -//loader defaults -.umb-tree .umb-loader{ - height: 10px; margin: 10px 10px 10px 10px; -} - - -/*body.touch .umb-tree .icon{font-size: 19px;}*/ -body.touch .umb-tree ins{font-size: 14px; visibility: visible; padding: 7px;} -body.touch .umb-tree li > div { - padding-top: 8px; - padding-bottom: 8px; - font-size: 110%; -} - -// change height of this if touch devices should have a different height of preloader. -body.touch .umb-tree li div.l div { - padding: 0; -} - -body.touch .umb-actions a { - padding: 7px 25px 7px 20px; - font-size: 110%; -} diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_cursor.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_cursor.less new file mode 100644 index 0000000000..dade8e0aae --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_cursor.less @@ -0,0 +1,39 @@ +/* + CURSORS +*/ + +.cursor-auto { cursor: auto; } +.cursor-default { cursor: default; } +.cursor-none { cursor: none; } +.cursor-context-menu { cursor: context-menu; } +.cursor-help { cursor: help; } +.cursor-pointer { cursor: pointer; } +.cursor-progress { cursor: progress; } +.cursor-wait { cursor: wait; } +.cursor-cell { cursor: cell; } +.cursor-crosshair { cursor: crosshair; } +.cursor-text { cursor: text; } +.cursor-vertical-text { cursor: vertical-text; } +.cursor-alias { cursor: alias; } +.cursor-copy { cursor: copy; } +.cursor-move { cursor: move; } +.cursor-no-drop { cursor: no-drop; } +.cursor-not-allowed { cursor: not-allowed; } +.cursor-e-resize { cursor: e-resize; } +.cursor-n-resize { cursor: n-resize; } +.cursor-ne-resize { cursor: ne-resize; } +.cursor-nw-resize { cursor: nw-resize; } +.cursor-s-resize { cursor: s-resize; } +.cursor-se-resize { cursor: se-resize; } +.cursor-sw-resize { cursor: sw-resize; } +.cursor-w-resize { cursor: w-resize; } +.cursor-ew-resize { cursor: ew-resize; } +.cursor-ns-resize { cursor: ns-resize; } +.cursor-nesw-resize { cursor: nesw-resize; } +.cursor-nwse-resize { cursor: nwse-resize; } +.cursor-col-resize { cursor: col-resize; } +.cursor-row-resize { cursor: row-resize; } +.cursor-all-scroll { cursor: all-scroll; } +.cursor-zoom-in { cursor: zoom-in; } +.cursor-zoom-out { cursor: zoom-out; } + diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index c6e87a74fe..513c645365 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -195,7 +195,7 @@ @bodyBackground: @gray-10; @textColor: @gray-2; -@editorHeaderHeight: 80px; +@editorHeaderHeight: 70px; @editorFooterHeight: 50px; @@ -331,6 +331,10 @@ @zindexUmbOverlay: 7500; @zindexOverlayBackdrop: 2000; +// these are used for the tour which should be on top of everything else +@zindexTourBackdrop: 9999; +@zindexTourModal: 10000; + // Sticky bar has a z-index of "500", which is set from javascript in directive // so set z-index of cropper should be lower to be behind sticky bar. @zindexCropperOverlay: 499; diff --git a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js new file mode 100644 index 0000000000..beb6353bde --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -0,0 +1,168 @@ + +/*********************************************************************************************************/ +/* Preview app and controller */ +/*********************************************************************************************************/ + +var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.services']) + + .controller("previewController", function ($scope, $http, $window, $timeout, $location, dialogService) { + + //gets a real query string value + function getParameterByName(name, url) { + if (!url) url = $window.location.href; + name = name.replace(/[\[\]]/g, '\\$&'); + var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, ' ')); + } + + function configureSignalR(iframe) { + // signalr hub + var previewHub = $.connection.previewHub; + + // visibility tracking + var dirtyContent = false; + var visibleContent = true; + + document.addEventListener('visibilitychange', function () { + visibleContent = !document.hidden; + if (visibleContent && dirtyContent) { + dirtyContent = false; + console.log("Visible, reloading.") + var iframeDoc = (iframe.contentWindow || iframe.contentDocument); + iframeDoc.location.reload(); + } + }); + + previewHub.client.refreshed = function (message, sender) { + console.log("Notified by SignalR preview hub (" + message + ")."); + + if ($scope.pageId != message) { + console.log("Not a notification for us (" + $scope.pageId + ")."); + return; + } + + if (!visibleContent) { + console.log("Not visible, will reload."); + dirtyContent = true; + return; + } + + console.log("Reloading."); + var iframeDoc = (iframe.contentWindow || iframe.contentDocument); + iframeDoc.location.reload(); + }; + + $.connection.hub.start() + .done(function () { console.log("Connected to SignalR preview hub (ID=" + $.connection.hub.id + ")"); }) + .fail(function () { console.log("Could not connect to SignalR preview hub."); }); + } + + var isInit = getParameterByName("init"); + if (isInit === "true") { + //do not continue, this is the first load of this new window, if this is passed in it means it's been + //initialized by the content editor and then the content editor will actually re-load this window without + //this flag. This is a required trick to get around chrome popup mgr. + return; + } + + $scope.pageId = $location.search().id || getParameterByName("id"); + var culture = $location.search().culture || getParameterByName("culture"); + + if ($scope.pageId) { + var query = 'id=' + $scope.pageId; + if (culture) { + query += "&culture=" + culture; + } + $scope.pageUrl = "frame?" + query; + } + + $scope.isOpen = false; + $scope.frameLoaded = false; + + $scope.valueAreLoaded = false; + $scope.devices = [ + { name: "desktop", css: "desktop", icon: "icon-display", title: "Desktop" }, + { name: "laptop - 1366px", css: "laptop border", icon: "icon-laptop", title: "Laptop" }, + { name: "iPad portrait - 768px", css: "iPad-portrait border", icon: "icon-ipad", title: "Tablet portrait" }, + { name: "iPad landscape - 1024px", css: "iPad-landscape border", icon: "icon-ipad flip", title: "Tablet landscape" }, + { name: "smartphone portrait - 480px", css: "smartphone-portrait border", icon: "icon-iphone", title: "Smartphone portrait" }, + { name: "smartphone landscape - 320px", css: "smartphone-landscape border", icon: "icon-iphone flip", title: "Smartphone landscape" } + ]; + $scope.previewDevice = $scope.devices[0]; + + /*****************************************************************************/ + /* Preview devices */ + /*****************************************************************************/ + + // Set preview device + $scope.updatePreviewDevice = function (device) { + $scope.previewDevice = device; + }; + + /*****************************************************************************/ + /* Exit Preview */ + /*****************************************************************************/ + + $scope.exitPreview = function () { + window.top.location.href = "../endPreview.aspx?redir=%2f" + $scope.pageId; + }; + + $scope.onFrameLoaded = function (iframe) { + $scope.frameLoaded = true; + configureSignalR(iframe); + } + + /*****************************************************************************/ + /* Panel managment */ + /*****************************************************************************/ + + $scope.openPreviewDevice = function () { + $scope.showDevicesPreview = true; + } + + }) + + + .component('previewIFrame', { + + template: "
", + controller: function ($element, $scope, angularHelper) { + + var vm = this; + + vm.$postLink = function () { + var resultFrame = $element.find("#resultFrame"); + resultFrame.on("load", iframeReady); + vm.srcDelayed = vm.src; + }; + + function iframeReady() { + var iframe = $element.find("#resultFrame").get(0); + hideUmbracoPreviewBadge(iframe); + angularHelper.safeApply($scope, function () { + vm.onLoaded({ iframe: iframe }); + }); + } + + function hideUmbracoPreviewBadge (iframe) { + if (iframe && iframe.contentDocument && iframe.contentDocument.getElementById("umbracoPreviewBadge")) { + iframe.contentDocument.getElementById("umbracoPreviewBadge").style.display = "none"; + } + }; + + }, + controllerAs: "vm", + bindings: { + src: "<", + onLoaded: "&" + } + + }) + + .config(function ($locationProvider) { + $locationProvider.html5Mode(false); //turn html5 mode off + $locationProvider.hashPrefix(''); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html index c4e13f9289..4cc5eba4a0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html @@ -4,7 +4,7 @@ -
+
- +
+
[UmbracoRequireHttps] - [DisableClientCache] + [DisableBrowserCache] public class BackOfficeController : UmbracoController { private readonly ManifestParser _manifestParser; @@ -85,6 +86,16 @@ namespace Umbraco.Web.Editors [HttpGet] public async Task VerifyInvite(string invite) { + //if you are hitting VerifyInvite, you're already signed in as a different user, and the token is invalid + //you'll exit on one of the return RedirectToAction("Default") but you're still logged in so you just get + //dumped at the default admin view with no detail + if(Security.IsAuthenticated()) + { + AuthenticationManager.SignOut( + Core.Constants.Security.BackOfficeAuthenticationType, + Core.Constants.Security.BackOfficeExternalAuthenticationType); + } + if (invite == null) { Logger.Warn("VerifyUser endpoint reached with invalid token: NULL"); @@ -128,16 +139,15 @@ namespace Umbraco.Web.Editors if (result.Succeeded == false) { Logger.Warn("Could not verify email, Error: " + string.Join(",", result.Errors) + ", Token: " + invite); - return RedirectToAction("Default"); + return new RedirectResult(Url.Action("Default") + "#/login/false?invite=3"); } //sign the user in - - AuthenticationManager.SignOut( - Core.Constants.Security.BackOfficeAuthenticationType, - Core.Constants.Security.BackOfficeExternalAuthenticationType); - + DateTime? previousLastLoginDate = identityUser.LastLoginDateUtc; await SignInManager.SignInAsync(identityUser, false, false); + //reset the lastlogindate back to previous as the user hasn't actually logged in, to add a flag or similar to SignInManager would be a breaking change + identityUser.LastLoginDateUtc = previousLastLoginDate; + await UserManager.UpdateAsync(identityUser); return new RedirectResult(Url.Action("Default") + "#/login/false?invite=1"); } @@ -195,7 +205,8 @@ namespace Umbraco.Web.Editors //get the legacy ActionJs file references to append as well var legacyActionJsRef = GetLegacyActionJs(LegacyJsActionType.JsUrl); - var result = initJs.GetJavascriptInitialization(HttpContext, JsInitialization.GetDefaultInitialization(), legacyActionJsRef); + var files = initJs.OptimizeBackOfficeScriptFiles(HttpContext, JsInitialization.GetDefaultInitialization(), legacyActionJsRef); + var result = JsInitialization.GetJavascriptInitialization(HttpContext, files, "umbraco"); result += initCss.GetStylesheetInitialization(HttpContext); return JavaScript(result); @@ -214,7 +225,7 @@ namespace Umbraco.Web.Editors var initJs = new JsInitialization(_manifestParser); var initCss = new CssInitialization(_manifestParser); var assets = new List(); - assets.AddRange(initJs.GetScriptFiles(HttpContext, Enumerable.Empty())); + assets.AddRange(initJs.OptimizeBackOfficeScriptFiles(HttpContext, Enumerable.Empty())); assets.AddRange(initCss.GetStylesheetFiles(HttpContext)); return new JArray(assets); } diff --git a/src/Umbraco.Web/Editors/BackOfficeModel.cs b/src/Umbraco.Web/Editors/BackOfficeModel.cs index 75a388ee80..9833121301 100644 --- a/src/Umbraco.Web/Editors/BackOfficeModel.cs +++ b/src/Umbraco.Web/Editors/BackOfficeModel.cs @@ -3,6 +3,7 @@ using Umbraco.Web.Features; namespace Umbraco.Web.Editors { + public class BackOfficeModel { public BackOfficeModel(UmbracoFeatures features, IGlobalSettings globalSettings) diff --git a/src/Umbraco.Web/Editors/BackOfficePreviewModel.cs b/src/Umbraco.Web/Editors/BackOfficePreviewModel.cs new file mode 100644 index 0000000000..1298575e50 --- /dev/null +++ b/src/Umbraco.Web/Editors/BackOfficePreviewModel.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Configuration; +using Umbraco.Web.Features; + +namespace Umbraco.Web.Editors +{ + public class BackOfficePreviewModel : BackOfficeModel + { + public BackOfficePreviewModel(UmbracoFeatures features, IGlobalSettings globalSettings) : base(features, globalSettings) + { + } + + public bool DisableDevicePreview => Features.Disabled.DisableDevicePreview; + public string PreviewExtendedHeaderView => Features.Enabled.PreviewExtendedView; + } +} diff --git a/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs b/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs index e2e896f30d..a3cb1d229a 100644 --- a/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs +++ b/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs @@ -67,7 +67,7 @@ namespace Umbraco.Web.Editors.Binders //map the property dto collection with the culture of the current variant variant.PropertyCollectionDto = Mapper.Map( model.PersistedContent, - options => options.Items[ResolutionContextExtensions.CultureKey] = variant.Culture); + options => options.SetCulture(variant.Culture)); } //now map all of the saved values to the dto diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 8c3cc5f557..1fcee6d727 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Net; using System.Net.Http; @@ -8,6 +9,7 @@ using System.Text; using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.ModelBinding; +using System.Web.Http.ValueProviders; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Logging; @@ -23,6 +25,7 @@ using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Persistence.Querying; using Umbraco.Web.PublishedCache; using Umbraco.Core.Events; +using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Models.Validation; using Umbraco.Web.Composing; using Umbraco.Web.Models; @@ -31,6 +34,7 @@ using Umbraco.Web._Legacy.Actions; using Constants = Umbraco.Core.Constants; using Language = Umbraco.Web.Models.ContentEditing.Language; using Umbraco.Core.PropertyEditors; +using Umbraco.Web.ContentApps; using Umbraco.Web.Editors.Binders; using Umbraco.Web.Editors.Filters; @@ -51,12 +55,16 @@ namespace Umbraco.Web.Editors { private readonly IPublishedSnapshotService _publishedSnapshotService; private readonly PropertyEditorCollection _propertyEditors; + private readonly Lazy> _allLangs; + + public object Domains { get; private set; } public ContentController(IPublishedSnapshotService publishedSnapshotService, PropertyEditorCollection propertyEditors) { if (publishedSnapshotService == null) throw new ArgumentNullException(nameof(publishedSnapshotService)); _publishedSnapshotService = publishedSnapshotService; _propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); + _allLangs = new Lazy>(() => Services.LocalizationService.GetAllLanguages().ToDictionary(x => x.IsoCode, x => x, StringComparer.InvariantCultureIgnoreCase)); } /// @@ -224,7 +232,7 @@ namespace Umbraco.Web.Editors public ContentItemDisplay GetRecycleBin() { var apps = new List(); - apps.AppendListViewApp(Services.DataTypeService, _propertyEditors, "recycleBin", "content"); + apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "content")); apps[0].Active = true; var display = new ContentItemDisplay { @@ -356,7 +364,7 @@ namespace Umbraco.Web.Editors var mapped = MapToDisplay(emptyContent); //remove the listview app if it exists - mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "childItems").ToList(); + mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); return mapped; } @@ -377,7 +385,7 @@ namespace Umbraco.Web.Editors var mapped = Mapper.Map(blueprint); //remove the listview app if it exists - mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "childItems").ToList(); + mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); return mapped; } @@ -453,10 +461,11 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, - string filter = "") + string filter = "", + string cultureName = "") // TODO it's not a NAME it's the ISO CODE { long totalChildren; - IContent[] children; + List children; if (pageNumber > 0 && pageSize > 0) { IQuery queryFilter = null; @@ -468,16 +477,14 @@ namespace Umbraco.Web.Editors } children = Services.ContentService - .GetPagedChildren( - id, (pageNumber - 1), pageSize, - out totalChildren, - orderBy, orderDirection, orderBySystemField, - queryFilter).ToArray(); + .GetPagedChildren(id, pageNumber - 1, pageSize, out totalChildren, + queryFilter, + Ordering.By(orderBy, orderDirection, cultureName, !orderBySystemField)).ToList(); } else { - children = Services.ContentService.GetChildren(id).ToArray(); - totalChildren = children.Length; + children = Services.ContentService.GetChildren(id).ToList(); + totalChildren = children.Count; } if (totalChildren == 0) @@ -490,12 +497,14 @@ namespace Umbraco.Web.Editors Mapper.Map>(content, opts => { + + opts.SetCulture(cultureName); + // if there's a list of property aliases to map - we will make sure to store this in the mapping context. - if (String.IsNullOrWhiteSpace(includeProperties) == false) - { - opts.Items["IncludeProperties"] = includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries); - } - })); + if (!includeProperties.IsNullOrWhiteSpace()) + opts.SetIncludedProperties(includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries)); + })) + .ToList(); // evaluate now return pagedResult; } @@ -577,6 +586,17 @@ namespace Umbraco.Web.Editors private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func saveMethod) { + //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name. + //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all + //uploaded files to being *only* the actual file name (as it should be). + if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) + { + foreach (var file in contentItem.UploadedFiles) + { + file.FileName = Path.GetFileName(file.FileName); + } + } + //If we've reached here it means: // * Our model has been bound // * and validated @@ -585,16 +605,22 @@ namespace Umbraco.Web.Editors // * Permissions are valid MapValuesForPersistence(contentItem); - //this a custom check for any variants not being flagged for Saving since we'll need to manually - //remove the ModelState validation for the Name - var variantIndex = 0; + //This a custom check for any variants not being flagged for Saving since we'll need to manually + //remove the ModelState validation for the Name. + //We are also tracking which cultures have an invalid Name + var variantCount = 0; + var variantNameErrors = new List(); foreach (var variant in contentItem.Variants) { - if (!variant.Save) + var msKey = $"Variants[{variantCount}].Name"; + if (ModelState.ContainsKey(msKey)) { - ModelState.Remove($"Variants[{variantIndex}].Name"); + if (!variant.Save) + ModelState.Remove(msKey); + else + variantNameErrors.Add(variant.Culture); } - variantIndex++; + variantCount++; } //We need to manually check the validation results here because: @@ -605,6 +631,16 @@ namespace Umbraco.Web.Editors // a message indicating this if (ModelState.IsValid == false) { + //another special case, if there's more than 1 variant, then we need to add the culture specific error + //messages based on the variants in error so that the messages show in the publish/save dialog + if (variantCount > 1) + { + foreach (var c in variantNameErrors) + { + AddCultureValidationError(c, "speechBubbles/contentCultureValidationError"); + } + } + if (IsCreatingAction(contentItem.Action)) { if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(contentItem) @@ -621,103 +657,162 @@ namespace Umbraco.Web.Editors } } - //if the model state is not valid we cannot publish so change it to save - switch (contentItem.Action) + //if there's only one variant and the model state is not valid we cannot publish so change it to save + if (variantCount == 1) { - case ContentSaveAction.Publish: - contentItem.Action = ContentSaveAction.Save; - break; - case ContentSaveAction.PublishNew: - contentItem.Action = ContentSaveAction.SaveNew; - break; + switch (contentItem.Action) + { + case ContentSaveAction.Publish: + contentItem.Action = ContentSaveAction.Save; + break; + case ContentSaveAction.PublishNew: + contentItem.Action = ContentSaveAction.SaveNew; + break; + } } + } //initialize this to successful var publishStatus = new PublishResult(null, contentItem.PersistedContent); - var wasCancelled = false; + bool wasCancelled; - if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) + //used to track successful notifications + var globalNotifications = new SimpleNotificationModel(); + var notifications = new Dictionary { - //save the item - var saveResult = saveMethod(contentItem.PersistedContent); + //global (non variant specific) notifications + [string.Empty] = globalNotifications + }; - wasCancelled = saveResult.Success == false && saveResult.Result == OperationResultType.FailedCancelledByEvent; - } - else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) + switch (contentItem.Action) { - var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); - wasCancelled = sendResult == false; - } - else - { - PublishInternal(contentItem, ref publishStatus, ref wasCancelled); + case ContentSaveAction.Save: + case ContentSaveAction.SaveNew: + var saveResult = saveMethod(contentItem.PersistedContent); + wasCancelled = saveResult.Success == false && saveResult.Result == OperationResultType.FailedCancelledByEvent; + if (saveResult.Success) + { + if (variantCount > 1) + { + var cultureErrors = ModelState.GetCulturesWithPropertyErrors(); + foreach (var c in contentItem.Variants.Where(x => x.Save && !cultureErrors.Contains(x.Culture)).Select(x => x.Culture).ToArray()) + { + AddSuccessNotification(notifications, c, + Services.TextService.Localize("speechBubbles/editContentSavedHeader"), + Services.TextService.Localize("speechBubbles/editVariantSavedText", new[] {_allLangs.Value[c].CultureName})); + } + } + else if (ModelState.IsValid) + { + globalNotifications.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentSavedHeader"), + Services.TextService.Localize("speechBubbles/editContentSavedText")); + } + } + break; + case ContentSaveAction.SendPublish: + case ContentSaveAction.SendPublishNew: + var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); + wasCancelled = sendResult == false; + if (sendResult) + { + if (variantCount > 1) + { + var cultureErrors = ModelState.GetCulturesWithPropertyErrors(); + foreach (var c in contentItem.Variants.Where(x => x.Save && !cultureErrors.Contains(x.Culture)).Select(x => x.Culture).ToArray()) + { + AddSuccessNotification(notifications, c, + Services.TextService.Localize("speechBubbles/editContentSendToPublish"), + Services.TextService.Localize("speechBubbles/editVariantSendToPublishText", new[] { _allLangs.Value[c].CultureName })); + } + } + else if (ModelState.IsValid) + { + globalNotifications.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentSendToPublish"), + Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); + } + } + break; + case ContentSaveAction.Publish: + case ContentSaveAction.PublishNew: + PublishInternal(contentItem, ref publishStatus, out wasCancelled, out var successfulCultures); + //global notifications + AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); + //variant specific notifications + foreach (var c in successfulCultures) + { + AddMessageForPublishStatus(publishStatus, notifications.GetOrCreate(c), successfulCultures); + } + break; + default: + throw new ArgumentOutOfRangeException(); } //get the updated model var display = MapToDisplay(contentItem.PersistedContent); + //merge the tracked success messages with the outgoing model + display.Notifications.AddRange(globalNotifications.Notifications); + foreach (var v in display.Variants.Where(x => x.Language != null)) + { + if (notifications.TryGetValue(v.Language.IsoCode, out var n)) + v.Notifications.AddRange(n.Notifications); + } + //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 HandleInvalidModelState(display); - //put the correct msgs in - switch (contentItem.Action) + if (wasCancelled) { - case ContentSaveAction.Save: - case ContentSaveAction.SaveNew: - if (wasCancelled == false) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSavedHeader"), - Services.TextService.Localize("speechBubbles/editContentSavedText")); - } - else - { - AddCancelMessage(display); - } - break; - case ContentSaveAction.SendPublish: - case ContentSaveAction.SendPublishNew: - if (wasCancelled == false) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSendToPublish"), - Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); - } - else - { - AddCancelMessage(display); - } - break; - case ContentSaveAction.Publish: - case ContentSaveAction.PublishNew: - ShowMessageForPublishStatus(publishStatus, display); - break; + AddCancelMessage(display); + if (IsCreatingAction(contentItem.Action)) + { + //If the item is new and the operation was cancelled, we need to return a different + // status code so the UI can handle it since it won't be able to redirect since there + // is no Id to redirect to! + throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); + } } - - //If the item is new and the operation was cancelled, we need to return a different - // status code so the UI can handle it since it won't be able to redirect since there - // is no Id to redirect to! - if (wasCancelled && IsCreatingAction(contentItem.Action)) - { - throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); - } - + display.PersistedContent = contentItem.PersistedContent; return display; } + /// + /// Used to add success notifications globally and for the culture + /// + /// + /// + /// + /// + /// + /// global notifications will be shown if all variant processing is successful and the save/publish dialog is closed, otherwise + /// variant specific notifications are used to show success messagse in the save/publish dialog. + /// + private static void AddSuccessNotification(IDictionary notifications, string culture, string header, string msg) + { + //add the global notification (which will display globally if all variants are successfully processed) + notifications[string.Empty].AddSuccessNotification(header, msg); + //add the variant specific notification (which will display in the dialog if all variants are not successfully processed) + notifications.GetOrCreate(culture).AddSuccessNotification(header, msg); + } + /// /// Performs the publishing operation for a content item /// /// /// /// + /// + /// if the content is variant this will return an array of cultures that will be published (passed validation rules) + /// /// /// If this is a culture variant than we need to do some validation, if it's not we'll publish as normal /// - private void PublishInternal(ContentItemSave contentItem, ref PublishResult publishStatus, ref bool wasCancelled) + private void PublishInternal(ContentItemSave contentItem, ref PublishResult publishStatus, out bool wasCancelled, out string[] successfulCultures) { if (publishStatus == null) throw new ArgumentNullException(nameof(publishStatus)); @@ -726,40 +821,31 @@ namespace Umbraco.Web.Editors //its invariant, proceed normally publishStatus = Services.ContentService.SaveAndPublish(contentItem.PersistedContent, userId: Security.CurrentUser.Id); wasCancelled = publishStatus.Result == PublishResultType.FailedCancelledByEvent; + successfulCultures = Array.Empty(); } else { - var canPublish = true; - //All variants in this collection should have a culture if we get here! but we'll double check and filter here var cultureVariants = contentItem.Variants.Where(x => !x.Culture.IsNullOrWhiteSpace()).ToList(); - //check if we are publishing other variants and validate them - var allLangs = Services.LocalizationService.GetAllLanguages().ToDictionary(x => x.IsoCode, x => x, StringComparer.InvariantCultureIgnoreCase); - - //validate any mandatory variants that are not in the list - var mandatoryLangs = Mapper.Map, IEnumerable>(allLangs.Values).Where(x => x.Mandatory); + //validate if we can publish based on the mandatory language requirements + var canPublish = ValidatePublishingMandatoryLanguages(contentItem, cultureVariants); - foreach (var lang in mandatoryLangs) + //Now check if there are validation errors on each variant. + //If validation errors are detected on a variant and it's state is set to 'publish', then we + //need to change it to 'save'. + //It is a requirement that this is performed AFTER ValidatePublishingMandatoryLanguages. + var cultureErrors = ModelState.GetCulturesWithPropertyErrors(); + foreach (var variant in contentItem.Variants) { - //Check if a mandatory language is missing from being published - - var variant = cultureVariants.First(x => x.Culture == lang.IsoCode); - var isPublished = contentItem.PersistedContent.IsCulturePublished(lang.IsoCode); - var isPublishing = variant.Publish; - - if (!isPublished && !isPublishing) - { - //cannot continue publishing since a required language that is not currently being published isn't published - AddCultureValidationError(lang.IsoCode, allLangs, "speechBubbles/contentReqCulturePublishError"); - canPublish = false; - } + if (cultureErrors.Contains(variant.Culture)) + variant.Publish = false; } - + if (canPublish) { //try to publish all the values on the model - canPublish = PublishCulture(contentItem.PersistedContent, cultureVariants, allLangs); + canPublish = PublishCulture(contentItem.PersistedContent, cultureVariants); } if (canPublish) @@ -767,6 +853,7 @@ namespace Umbraco.Web.Editors //proceed to publish if all validation still succeeds publishStatus = Services.ContentService.SavePublishing(contentItem.PersistedContent, Security.CurrentUser.Id); wasCancelled = publishStatus.Result == PublishResultType.FailedCancelledByEvent; + successfulCultures = contentItem.Variants.Where(x => x.Publish).Select(x => x.Culture).ToArray(); } else { @@ -774,18 +861,49 @@ namespace Umbraco.Web.Editors var saveResult = Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id); publishStatus = new PublishResult(PublishResultType.FailedCannotPublish, null, contentItem.PersistedContent); wasCancelled = saveResult.Result == OperationResultType.FailedCancelledByEvent; + successfulCultures = Array.Empty(); } } } + /// + /// Validate if publishing is possible based on the mandatory language requirements + /// + /// + /// + /// + private bool ValidatePublishingMandatoryLanguages(ContentItemSave contentItem, IReadOnlyCollection cultureVariants) + { + var canPublish = true; + + //validate any mandatory variants that are not in the list + var mandatoryLangs = Mapper.Map, IEnumerable>(_allLangs.Value.Values).Where(x => x.IsMandatory); + + foreach (var lang in mandatoryLangs) + { + //Check if a mandatory language is missing from being published + + var variant = cultureVariants.First(x => x.Culture == lang.IsoCode); + var isPublished = contentItem.PersistedContent.IsCulturePublished(lang.IsoCode); + var isPublishing = variant.Publish; + + if (isPublished || isPublishing) continue; + + //cannot continue publishing since a required language that is not currently being published isn't published + AddCultureValidationError(lang.IsoCode, "speechBubbles/contentReqCulturePublishError"); + canPublish = false; + } + + return canPublish; + } + /// /// This will call PublishCulture on the content item for each culture that needs to be published including the invariant culture /// /// /// - /// /// - private bool PublishCulture(IContent persistentContent, IEnumerable cultureVariants, IDictionary allLangs) + private bool PublishCulture(IContent persistentContent, IEnumerable cultureVariants) { foreach(var variant in cultureVariants.Where(x => x.Publish)) { @@ -793,7 +911,7 @@ namespace Umbraco.Web.Editors var valid = persistentContent.PublishCulture(variant.Culture); if (!valid) { - AddCultureValidationError(variant.Culture, allLangs, "speechBubbles/contentCultureValidationError"); + AddCultureValidationError(variant.Culture, "speechBubbles/contentCultureValidationError"); return false; } } @@ -805,16 +923,15 @@ namespace Umbraco.Web.Editors /// Adds a generic culture error for use in displaying the culture validation error in the save/publish dialogs /// /// - /// /// - private void AddCultureValidationError(string culture, IDictionary allLangs, string localizationKey) + private void AddCultureValidationError(string culture, string localizationKey) { var key = "_content_variant_" + culture + "_"; if (ModelState.ContainsKey(key)) return; - var errMsg = Services.TextService.Localize(localizationKey, new[] { allLangs[culture].CultureName }); + var errMsg = Services.TextService.Localize(localizationKey, new[] { _allLangs.Value[culture].CultureName }); ModelState.AddModelError(key, errMsg); } - + /// /// Publishes a document with a given ID /// @@ -839,7 +956,7 @@ namespace Umbraco.Web.Editors if (publishResult.Success == false) { var notificationModel = new SimpleNotificationModel(); - ShowMessageForPublishStatus(publishResult, notificationModel); + AddMessageForPublishStatus(publishResult, notificationModel); return Request.CreateValidationErrorResponse(notificationModel); } @@ -1008,39 +1125,87 @@ namespace Umbraco.Web.Editors /// /// Unpublishes a node with a given Id and returns the unpublished entity /// - /// The content id to unpublish - /// The culture variant for the content id to unpublish, if none specified will unpublish all variants of the content + /// The content and variants to unpublish /// - [EnsureUserPermissionForContent("id", 'U')] + [EnsureUserPermissionForContent("model.Id", 'U')] [OutgoingEditorModelEvent] - public ContentItemDisplay PostUnPublish(int id, string culture = null) + public ContentItemDisplay PostUnpublish(UnpublishContent model) { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(model.Id)); if (foundContent == null) - HandleContentNotFound(id); + HandleContentNotFound(model.Id); - var unpublishResult = Services.ContentService.Unpublish(foundContent, culture: culture, userId: Security.GetUserId().ResultOr(0)); - - var content = MapToDisplay(foundContent); - - if (!unpublishResult.Success) + var languageCount = _allLangs.Value.Count(); + if (model.Cultures.Length == 0 || model.Cultures.Length == languageCount) { - AddCancelMessage(content); - throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); + //this means that the entire content item will be unpublished + var unpublishResult = Services.ContentService.Unpublish(foundContent, userId: Security.GetUserId().ResultOr(0)); + + var content = MapToDisplay(foundContent); + + if (!unpublishResult.Success) + { + AddCancelMessage(content); + throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); + } + else + { + content.AddSuccessNotification( + Services.TextService.Localize("content/unpublish"), + Services.TextService.Localize("speechBubbles/contentUnpublished")); + return content; + } } else { - //fixme should have a better localized method for when we have the UnpublishResultType.SuccessMandatoryCulture status + //we only want to unpublish some of the variants + var results = new Dictionary(); + foreach(var c in model.Cultures) + { + var result = Services.ContentService.Unpublish(foundContent, culture: c, userId: Security.GetUserId().ResultOr(0)); + results[c] = result; + if (result.Result == UnpublishResultType.SuccessMandatoryCulture) + { + //if this happens, it means they are all unpublished, we don't need to continue + break; + } + } - content.AddSuccessNotification( - Services.TextService.Localize("content/unPublish"), - unpublishResult.Result == UnpublishResultType.SuccessCulture - ? Services.TextService.Localize("speechBubbles/contentVariationUnpublished", new[] { culture }) - : Services.TextService.Localize("speechBubbles/contentUnpublished")); + var content = MapToDisplay(foundContent); + //check for this status and return the correct message + if (results.Any(x => x.Value.Result == UnpublishResultType.SuccessMandatoryCulture)) + { + content.AddSuccessNotification( + Services.TextService.Localize("content/unpublish"), + Services.TextService.Localize("speechBubbles/contentMandatoryCultureUnpublished")); + return content; + } + + //otherwise add a message for each one unpublished + foreach (var r in results) + { + content.AddSuccessNotification( + Services.TextService.Localize("content/unpublish"), + Services.TextService.Localize("speechBubbles/contentCultureUnpublished", new[] { _allLangs.Value[r.Key].CultureName })); + } return content; + } + + } + + public ContentDomainsAndCulture GetCultureAndDomains(int id) + { + var nodeDomains = Services.DomainService.GetAssignedDomains(id, true).ToArray(); + var wildcard = nodeDomains.FirstOrDefault(d => d.IsWildcard); + var domains = nodeDomains.Where(d => !d.IsWildcard).Select(d => new DomainDisplay(d.DomainName, d.LanguageId.GetValueOrDefault(0))); + return new ContentDomainsAndCulture + { + Domains = domains, + Language = wildcard == null || !wildcard.LanguageId.HasValue ? "undefined" : wildcard.LanguageId.ToString() + }; } [HttpPost] @@ -1194,19 +1359,11 @@ namespace Umbraco.Web.Editors if (!ModelState.IsValid) { //Add any culture specific errors here - var cultureErrors = ModelState.Keys - .Select(x => x.Split('.')) //split into parts - .Where(x => x.Length >= 3 && x[0] == "_Properties") //only choose _Properties errors - .Select(x => x[2]) //select the culture part - .Where(x => !x.IsNullOrWhiteSpace()) //if it has a value - .Distinct() - .ToList(); - - var allLangs = Services.LocalizationService.GetAllLanguages().ToDictionary(x => x.IsoCode, x => x, StringComparer.InvariantCultureIgnoreCase); + var cultureErrors = ModelState.GetCulturesWithPropertyErrors(); foreach (var cultureError in cultureErrors) { - AddCultureValidationError(cultureError, allLangs, "speechBubbles/contentCultureValidationError"); + AddCultureValidationError(cultureError, "speechBubbles/contentCultureValidationError"); } } @@ -1347,15 +1504,35 @@ namespace Umbraco.Web.Editors return toMove; } - private void ShowMessageForPublishStatus(PublishResult status, INotificationModel display) + /// + /// Adds notification messages to the outbound display model for a given published status + /// + /// + /// + /// + /// This is null when dealing with invariant content, else it's the cultures that were succesfully published + /// + private void AddMessageForPublishStatus(PublishResult status, INotificationModel display, string[] successfulCultures = null) { switch (status.Result) { case PublishResultType.Success: case PublishResultType.SuccessAlready: - display.AddSuccessNotification( + if (successfulCultures == null) + { + display.AddSuccessNotification( Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), Services.TextService.Localize("speechBubbles/editContentPublishedText")); + } + else + { + foreach (var c in successfulCultures) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), + Services.TextService.Localize("speechBubbles/editVariantPublishedText", new[] { _allLangs.Value[c].CultureName })); + } + } break; case PublishResultType.FailedPathNotPublished: display.AddWarningNotification( @@ -1367,12 +1544,14 @@ namespace Umbraco.Web.Editors AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent"); break; case PublishResultType.FailedAwaitingRelease: + //TODO: We'll need to deal with variants here eventually display.AddWarningNotification( Services.TextService.Localize("publish"), Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", new[] { $"{status.Content.Name} ({status.Content.Id})" }).Trim()); break; case PublishResultType.FailedHasExpired: + //TODO: We'll need to deal with variants here eventually display.AddWarningNotification( Services.TextService.Localize("publish"), Services.TextService.Localize("publish/contentPublishedFailedExpired", @@ -1482,7 +1661,6 @@ namespace Umbraco.Web.Editors /// Used to map an instance to a and ensuring a language is present if required ///
/// - /// /// private ContentItemDisplay MapToDisplay(IContent content) { diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index ea385ceba6..9bb80cc2b3 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -194,7 +194,7 @@ namespace Umbraco.Web.Editors : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); } - public CreatedContentTypeCollectionResult PostCreateCollection(int parentId, string collectionName, string collectionItemName, string collectionIcon, string collectionItemIcon) + public CreatedContentTypeCollectionResult PostCreateCollection(int parentId, string collectionName, bool collectionCreateTemplate, string collectionItemName, bool collectionItemCreateTemplate, string collectionIcon, string collectionItemIcon) { var storeInContainer = false; var allowUnderDocType = -1; @@ -213,20 +213,38 @@ namespace Umbraco.Web.Editors // create item doctype var itemDocType = new ContentType(parentId); itemDocType.Name = collectionItemName; - itemDocType.Alias = collectionItemName.ToSafeAlias(); + itemDocType.Alias = collectionItemName.ToSafeAlias(true); itemDocType.Icon = collectionItemIcon; + + // create item doctype template + if (collectionItemCreateTemplate) + { + var template = CreateTemplateForContentType(itemDocType.Alias, itemDocType.Name); + itemDocType.SetDefaultTemplate(template); + } + + // save item doctype Services.ContentTypeService.Save(itemDocType); // create collection doctype var collectionDocType = new ContentType(parentId); collectionDocType.Name = collectionName; - collectionDocType.Alias = collectionName.ToSafeAlias(); + collectionDocType.Alias = collectionName.ToSafeAlias(true); collectionDocType.Icon = collectionIcon; collectionDocType.IsContainer = true; collectionDocType.AllowedContentTypes = new List() { new ContentTypeSort(itemDocType.Id, 0) }; + + // create collection doctype template + if (collectionCreateTemplate) + { + var template = CreateTemplateForContentType(collectionDocType.Alias, collectionDocType.Name); + collectionDocType.SetDefaultTemplate(template); + } + + // save collection doctype Services.ContentTypeService.Save(collectionDocType); // test if the parent id exist and then allow the collection underneath @@ -272,16 +290,7 @@ namespace Umbraco.Web.Editors //create a default template if it doesnt exist -but only if default template is == to the content type if (ctSave.DefaultTemplate.IsNullOrWhiteSpace() == false && ctSave.DefaultTemplate == ctSave.Alias) { - var template = Services.FileService.GetTemplate(ctSave.Alias); - if (template == null) - { - var tryCreateTemplate = Services.FileService.CreateTemplateForContentType(ctSave.Alias, ctSave.Name); - if (tryCreateTemplate == false) - { - Logger.Warn("Could not create a template for the Content Type: {ContentTypeAlias}, status: {CreateTemplateResult}", ctSave.Alias, tryCreateTemplate.Result.Result); - } - template = tryCreateTemplate.Result.Entity; - } + var template = CreateTemplateForContentType(ctSave.Alias, ctSave.Name); // If the alias has been manually updated before the first save, // make sure to also update the first allowed template, as the @@ -311,6 +320,24 @@ namespace Umbraco.Web.Editors return display; } + private ITemplate CreateTemplateForContentType(string contentTypeAlias, string contentTypeName) + { + var template = Services.FileService.GetTemplate(contentTypeAlias); + if (template == null) + { + var tryCreateTemplate = Services.FileService.CreateTemplateForContentType(contentTypeAlias, contentTypeName); + if (tryCreateTemplate == false) + { + Logger.Warn("Could not create a template for Content Type: \"{ContentTypeAlias}\", status: {Status}", + contentTypeAlias, tryCreateTemplate.Result.Result); + } + + template = tryCreateTemplate.Result.Entity; + } + + return template; + } + /// /// Returns an empty content type for use as a scaffold when creating a new type /// diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index 0764e18eeb..fa1aaf7345 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -206,9 +206,9 @@ namespace Umbraco.Web.Editors // works since that is based on aliases. var allAliases = Services.ContentTypeService.GetAllContentTypeAliases(); var exists = allAliases.InvariantContains(contentTypeSave.Alias); - if ((exists) && (ctId == 0 || ct.Alias != contentTypeSave.Alias)) + if (exists && (ctId == 0 || !ct.Alias.InvariantEquals(contentTypeSave.Alias))) { - ModelState.AddModelError("Alias", "A content type, media type or member type with this alias already exists"); + ModelState.AddModelError("Alias", Services.TextService.Localize("editcontenttype/aliasAlreadyExists")); } // execute the externam validators diff --git a/src/Umbraco.Web/Editors/DashboardController.cs b/src/Umbraco.Web/Editors/DashboardController.cs index 230fbbea36..6f42b17af6 100644 --- a/src/Umbraco.Web/Editors/DashboardController.cs +++ b/src/Umbraco.Web/Editors/DashboardController.cs @@ -28,14 +28,15 @@ namespace Umbraco.Web.Editors public class DashboardController : UmbracoApiController { public DashboardController() - { - } + { } public DashboardController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, CacheHelper applicationCache, ILogger logger, ProfilingLogger profilingLogger, IRuntimeState runtimeState) : base(globalSettings, umbracoContext, sqlContext, services, applicationCache, logger, profilingLogger, runtimeState) - { - } + { } + //we have just one instance of HttpClient shared for the entire application + private static readonly HttpClient HttpClient = new HttpClient(); + //we have baseurl as a param to make previewing easier, so we can test with a dev domain from client side [ValidateAngularAntiForgeryToken] public async Task GetRemoteDashboardContent(string section, string baseUrl = "https://dashboard.umbraco.org/") @@ -63,13 +64,10 @@ namespace Umbraco.Web.Editors //content is null, go get it try { - using (var web = new HttpClient()) - { - //fetch dashboard json and parse to JObject - var json = await web.GetStringAsync(url); - content = JObject.Parse(json); - result = content; - } + //fetch dashboard json and parse to JObject + var json = await HttpClient.GetStringAsync(url); + content = JObject.Parse(json); + result = content; ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); } @@ -102,17 +100,14 @@ namespace Umbraco.Web.Editors //content is null, go get it try { - using (var web = new HttpClient()) - { - //fetch remote css - content = await web.GetStringAsync(url); + //fetch remote css + content = await HttpClient.GetStringAsync(url); - //can't use content directly, modified closure problem - result = content; + //can't use content directly, modified closure problem + result = content; - //save server content for 30 mins - ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); - } + //save server content for 30 mins + ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); } catch (HttpRequestException ex) { diff --git a/src/Umbraco.Web/Editors/DashboardHelper.cs b/src/Umbraco.Web/Editors/DashboardHelper.cs index b40f289d04..75ccda1a9b 100644 --- a/src/Umbraco.Web/Editors/DashboardHelper.cs +++ b/src/Umbraco.Web/Editors/DashboardHelper.cs @@ -46,7 +46,9 @@ namespace Umbraco.Web.Editors var tabs = new List>(); var i = 1; - // The dashboard config can contain more than one area inserted by a package. + //disable packages section dashboard + if (section == "packages") return tabs; + foreach (var dashboardSection in UmbracoConfig.For.DashboardSettings().Sections.Where(x => x.Areas.Contains(section))) { //we need to validate access to this section diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index 7f95fdde54..e11ea7cbef 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -139,7 +139,7 @@ namespace Umbraco.Web.Editors /// public IEnumerable GetPreValues(string editorAlias, int dataTypeId = -1) { - var propEd = Current.PropertyEditors[editorAlias]; + var propEd = _propertyEditors[editorAlias]; if (propEd == null) { throw new InvalidOperationException("Could not find property editor with alias " + editorAlias); @@ -290,7 +290,7 @@ namespace Umbraco.Web.Editors /// [UmbracoApplicationAuthorize( Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Developer)] + Constants.Applications.Settings, Constants.Applications.Packages)] public IEnumerable GetAll() { return Services.DataTypeService @@ -307,7 +307,7 @@ namespace Umbraco.Web.Editors /// [UmbracoTreeAuthorize( Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Developer)] + Constants.Applications.Settings, Constants.Applications.Packages)] public IDictionary> GetGroupedDataTypes() { var dataTypes = Services.DataTypeService @@ -340,7 +340,7 @@ namespace Umbraco.Web.Editors /// [UmbracoTreeAuthorize( Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Developer)] + Constants.Applications.Settings, Constants.Applications.Packages)] public IDictionary> GetGroupedPropertyEditors() { var datatypes = new List(); @@ -373,7 +373,7 @@ namespace Umbraco.Web.Editors /// [UmbracoTreeAuthorize( Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Developer)] + Constants.Applications.Settings, Constants.Applications.Packages)] public IEnumerable GetAllPropertyEditors() { return Current.PropertyEditors diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 313693c08c..fcbe4bdd4c 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -10,15 +10,18 @@ using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using System.Linq; using System.Net.Http; +using System.Net.Http.Formatting; using Umbraco.Core.Models; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using System.Web.Http.Controllers; using Umbraco.Core.Models.Entities; using Umbraco.Core.Xml; +using Umbraco.Web.Models.Mapping; using Umbraco.Web.Search; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Editors { @@ -211,7 +214,7 @@ namespace Umbraco.Web.Editors } } - var ancestors = GetAncestors(id, type); + var ancestors = GetResultForAncestors(id, type); //if content, skip the first node for replicating NiceUrl defaults if(type == UmbracoEntityTypes.Document) { @@ -226,13 +229,7 @@ namespace Umbraco.Web.Editors }; } - [Obsolete("Use GetyById instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public EntityBasic GetByKey(Guid id, UmbracoEntityTypes type) - { - return GetResultForKey(id, type); - } - + /// /// Gets an entity by a xpath query /// @@ -588,9 +585,10 @@ namespace Umbraco.Web.Editors } } - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type) + [HttpQueryStringFilter("queryStrings")] + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, FormDataCollection queryStrings) { - return GetResultForAncestors(id, type); + return GetResultForAncestors(id, type, queryStrings); } /// @@ -632,7 +630,7 @@ namespace Umbraco.Web.Editors } } - private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType) + private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, FormDataCollection queryStrings = null) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) @@ -672,12 +670,14 @@ namespace Umbraco.Web.Editors ids = lids.ToArray(); } + var culture = queryStrings?.GetValue("culture"); + return ids.Length == 0 ? Enumerable.Empty() : Services.EntityService.GetAll(objectType.Value, ids) .WhereNotNull() .OrderBy(x => x.Level) - .Select(Mapper.Map); + .Select(x => Mapper.Map(x, opts => { opts.SetCulture(culture);})); } //now we need to convert the unknown ones switch (entityType) diff --git a/src/Umbraco.Web/Editors/HelpController.cs b/src/Umbraco.Web/Editors/HelpController.cs index 50e6547e36..ccbbcaeee8 100644 --- a/src/Umbraco.Web/Editors/HelpController.cs +++ b/src/Umbraco.Web/Editors/HelpController.cs @@ -7,20 +7,22 @@ using System.Threading.Tasks; namespace Umbraco.Web.Editors { public class HelpController : UmbracoAuthorizedJsonController - { + { + private static HttpClient _httpClient; public async Task> GetContextHelpForPage(string section, string tree, string baseUrl = "https://our.umbraco.com") { var url = string.Format(baseUrl + "/Umbraco/Documentation/Lessons/GetContextHelpDocs?sectionAlias={0}&treeAlias={1}", section, tree); - using (var web = new HttpClient()) - { - //fetch dashboard json and parse to JObject - var json = await web.GetStringAsync(url); - var result = JsonConvert.DeserializeObject>(json); - if (result != null) - return result; - return new List(); - } + if (_httpClient == null) + _httpClient = new HttpClient(); + + //fetch dashboard json and parse to JObject + var json = await _httpClient.GetStringAsync(url); + var result = JsonConvert.DeserializeObject>(json); + if (result != null) + return result; + + return new List(); } } diff --git a/src/Umbraco.Web/Editors/ImagesController.cs b/src/Umbraco.Web/Editors/ImagesController.cs index a13e7d3a95..e9b89a0ab4 100644 --- a/src/Umbraco.Web/Editors/ImagesController.cs +++ b/src/Umbraco.Web/Editors/ImagesController.cs @@ -2,7 +2,9 @@ using System.IO; using System.Net; using System.Net.Http; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; +using Umbraco.Web.Media; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using Constants = Umbraco.Core.Constants; @@ -16,10 +18,12 @@ namespace Umbraco.Web.Editors public class ImagesController : UmbracoAuthorizedApiController { private readonly MediaFileSystem _mediaFileSystem; + private readonly IContentSection _contentSection; - public ImagesController(MediaFileSystem mediaFileSystem) + public ImagesController(MediaFileSystem mediaFileSystem, IContentSection contentSection) { _mediaFileSystem = mediaFileSystem; + _contentSection = contentSection; } /// @@ -51,7 +55,7 @@ namespace Umbraco.Web.Editors var ext = Path.GetExtension(imagePath); // we need to check if it is an image by extension - if (_mediaFileSystem.IsImageFile(ext) == false) + if (_contentSection.IsImageFile(ext) == false) return Request.CreateResponse(HttpStatusCode.NotFound); //redirect to ImageProcessor thumbnail with rnd generated from last modified time of original media file diff --git a/src/Umbraco.Web/Editors/LanguageController.cs b/src/Umbraco.Web/Editors/LanguageController.cs index 96019da702..a3e613670b 100644 --- a/src/Umbraco.Web/Editors/LanguageController.cs +++ b/src/Umbraco.Web/Editors/LanguageController.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; @@ -8,8 +7,6 @@ using System.Web.Http; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Persistence; -using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; @@ -56,27 +53,7 @@ namespace Umbraco.Web.Editors if (lang == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - var model = Mapper.Map(lang); - - //if there's only one language, by default it is the default - var allLangs = Services.LocalizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); - if (!lang.IsDefaultVariantLanguage) - { - if (allLangs.Count == 1) - { - model.IsDefaultVariantLanguage = true; - model.Mandatory = true; - } - else if (allLangs.All(x => !x.IsDefaultVariantLanguage)) - { - //if no language has the default flag, then the defaul language is the one with the lowest id - model.IsDefaultVariantLanguage = allLangs[0].Id == lang.Id; - model.Mandatory = allLangs[0].Id == lang.Id; - } - } - - - return model; + return Mapper.Map(lang); } /// @@ -88,16 +65,21 @@ namespace Umbraco.Web.Editors public IHttpActionResult DeleteLanguage(int id) { var language = Services.LocalizationService.GetLanguageById(id); - if (language == null) return NotFound(); - - var totalLangs = Services.LocalizationService.GetAllLanguages().Count(); - - if (language.IsDefaultVariantLanguage || totalLangs == 1) + if (language == null) { - var message = $"Language '{language.IsoCode}' is currently set to 'default' or it is the only installed language and can not be deleted."; + return NotFound(); + } + + // the service would not let us do it, but test here nevertheless + if (language.IsDefault) + { + var message = $"Language '{language.IsoCode}' is currently set to 'default' and can not be deleted."; throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse(message)); } + // service is happy deleting a language that's fallback for another language, + // will just remove it - so no need to check here + Services.LocalizationService.Delete(language); return Ok(); @@ -113,16 +95,17 @@ namespace Umbraco.Web.Editors if (!ModelState.IsValid) throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); - var found = Services.LocalizationService.GetLanguageByIsoCode(language.IsoCode); + // this is prone to race conds but the service will not let us proceed anyways + var existing = Services.LocalizationService.GetLanguageByIsoCode(language.IsoCode); - if (found != null && language.Id != found.Id) + if (existing != null && language.Id != existing.Id) { - //someone is trying to create a language that alraedy exist + //someone is trying to create a language that already exist ModelState.AddModelError("IsoCode", "The language " + language.IsoCode + " already exists"); throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); } - if (found == null) + if (existing == null) { CultureInfo culture; try @@ -135,22 +118,66 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); } - //create it - var newLang = new Umbraco.Core.Models.Language(culture.Name) + // create it (creating a new language cannot create a fallback cycle) + var newLang = new Core.Models.Language(culture.Name) { CultureName = culture.DisplayName, - IsDefaultVariantLanguage = language.IsDefaultVariantLanguage, - Mandatory = language.Mandatory + IsDefault = language.IsDefault, + IsMandatory = language.IsMandatory, + FallbackLanguageId = language.FallbackLanguageId }; + Services.LocalizationService.Save(newLang); return Mapper.Map(newLang); } - found.Mandatory = language.Mandatory; - found.IsDefaultVariantLanguage = language.IsDefaultVariantLanguage; - Services.LocalizationService.Save(found); - return Mapper.Map(found); + existing.IsMandatory = language.IsMandatory; + + // note that the service will prevent the default language from being "un-defaulted" + // but does not hurt to test here - though the UI should prevent it too + if (existing.IsDefault && !language.IsDefault) + { + ModelState.AddModelError("IsDefault", "Cannot un-default the default language."); + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + } + + existing.IsDefault = language.IsDefault; + existing.FallbackLanguageId = language.FallbackLanguageId; + + // modifying an existing language can create a fallback, verify + // note that the service will check again, dealing with race conds + if (existing.FallbackLanguageId.HasValue) + { + var languages = Services.LocalizationService.GetAllLanguages().ToDictionary(x => x.Id, x => x); + if (!languages.ContainsKey(existing.FallbackLanguageId.Value)) + { + ModelState.AddModelError("FallbackLanguage", "The selected fall back language does not exist."); + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + } + if (CreatesCycle(existing, languages)) + { + ModelState.AddModelError("FallbackLanguage", $"The selected fall back language {languages[existing.FallbackLanguageId.Value].IsoCode} would create a circular path."); + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + } + } + + Services.LocalizationService.Save(existing); + return Mapper.Map(existing); + } + + // see LocalizationService + private bool CreatesCycle(ILanguage language, IDictionary languages) + { + // a new language is not referenced yet, so cannot be part of a cycle + if (!language.HasIdentity) return false; + + var id = language.FallbackLanguageId; + while (true) // assuming languages does not already contains a cycle, this must end + { + if (!id.HasValue) return false; // no fallback means no cycle + if (id.Value == language.Id) return true; // back to language = cycle! + id = languages[id.Value].FallbackLanguageId; // else keep chaining + } } - } } diff --git a/src/Umbraco.Web/Editors/LogController.cs b/src/Umbraco.Web/Editors/LogController.cs index e36fe05de3..1205226b8f 100644 --- a/src/Umbraco.Web/Editors/LogController.cs +++ b/src/Umbraco.Web/Editors/LogController.cs @@ -1,10 +1,11 @@ -using System; +using AutoMapper; +using System; using System.Collections.Generic; using System.Linq; -using AutoMapper; -using Umbraco.Web.Models.ContentEditing; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; namespace Umbraco.Web.Editors @@ -45,7 +46,7 @@ namespace Umbraco.Web.Editors var userId = Security.GetUserId().ResultOr(0); var result = Services.AuditService.GetPagedItemsByUser(userId, pageNumber - 1, pageSize, out totalRecords, orderDirection, customFilter:dateQuery); var mapped = Mapper.Map>(result); - return new PagedResult(totalRecords, pageNumber + 1, pageSize) + return new PagedResult(totalRecords, pageNumber, pageSize) { Items = MapAvatarsAndNames(mapped) }; diff --git a/src/Umbraco.Web/Editors/MacroController.cs b/src/Umbraco.Web/Editors/MacroController.cs index d625e3a575..3c576befb9 100644 --- a/src/Umbraco.Web/Editors/MacroController.cs +++ b/src/Umbraco.Web/Editors/MacroController.cs @@ -135,7 +135,6 @@ namespace Umbraco.Web.Editors var legacyPage = new global::umbraco.page(doc, _variationContextAccessor); - UmbracoContext.HttpContext.Items["pageID"] = doc.Id; UmbracoContext.HttpContext.Items["pageElements"] = legacyPage.Elements; UmbracoContext.HttpContext.Items[global::Umbraco.Core.Constants.Conventions.Url.AltTemplate] = null; diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 70aa7e4da8..635cdfaa17 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -31,9 +31,11 @@ using Umbraco.Web.UI; using Notification = Umbraco.Web.Models.ContentEditing.Notification; using Umbraco.Core.Persistence; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Models.Editors; using Umbraco.Core.Models.Validation; using Umbraco.Core.PropertyEditors; +using Umbraco.Web.ContentApps; using Umbraco.Web.Editors.Binders; using Umbraco.Web.Editors.Filters; @@ -85,7 +87,7 @@ namespace Umbraco.Web.Editors var mapped = Mapper.Map(emptyContent); //remove the listview app if it exists - mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "childItems").ToList(); + mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); return mapped; } @@ -97,7 +99,7 @@ namespace Umbraco.Web.Editors public MediaItemDisplay GetRecycleBin() { var apps = new List(); - apps.AppendListViewApp(Services.DataTypeService, _propertyEditors, "recycleBin", "media"); + apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "media")); apps[0].Active = true; var display = new MediaItemDisplay { @@ -437,6 +439,17 @@ namespace Umbraco.Web.Editors [ModelBinder(typeof(MediaItemBinder))] MediaItemSave contentItem) { + //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name. + //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all + //uploaded files to being *only* the actual file name (as it should be). + if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) + { + foreach (var file in contentItem.UploadedFiles) + { + file.FileName = Path.GetFileName(file.FileName); + } + } + //If we've reached here it means: // * Our model has been bound // * and validated @@ -686,15 +699,7 @@ namespace Umbraco.Web.Editors mediaType = result.FormData["contentTypeAlias"]; } - //TODO: make the media item name "nice" since file names could be pretty ugly, we have - // string extensions to do much of this but we'll need: - // * Pascalcase the name (use string extensions) - // * strip the file extension - // * underscores to spaces - // * probably remove 'ugly' characters - let's discuss - // All of this logic should exist in a string extensions method and be unit tested - // http://issues.umbraco.org/issue/U4-5572 - var mediaItemName = fileName; + var mediaItemName = fileName.ToFriendlyName(); var f = mediaService.CreateMedia(mediaItemName, parentId, mediaType, Security.CurrentUser.Id); @@ -908,4 +913,4 @@ namespace Umbraco.Web.Editors return hasPathAccess; } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index aa03628632..9f70c3c33b 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -26,7 +26,9 @@ using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; using System.Collections.Generic; +using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.PropertyEditors; +using Umbraco.Web.ContentApps; using Umbraco.Web.Editors.Binders; using Umbraco.Web.Editors.Filters; @@ -137,7 +139,7 @@ namespace Umbraco.Web.Editors var name = foundType != null ? foundType.Name : listName; var apps = new List(); - apps.AppendListViewApp(Services.DataTypeService, _propertyEditors, listName, "member"); + apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, listName, "member")); apps[0].Active = true; var display = new MemberListDisplay diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index d2a0df68ba..6111a931e3 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -24,6 +24,7 @@ using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.UI; +using Umbraco.Web.UI.JavaScript; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using File = System.IO.File; @@ -36,7 +37,7 @@ namespace Umbraco.Web.Editors /// A controller used for installing packages and managing all of the data in the packages section in the back office /// [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorize(Core.Constants.Applications.Developer)] + [UmbracoApplicationAuthorize(Core.Constants.Applications.Packages)] public class PackageInstallController : UmbracoAuthorizedJsonController { /// @@ -582,8 +583,9 @@ namespace Umbraco.Web.Editors ins.LoadConfig(IOHelper.MapPath(model.TemporaryDirectoryPath)); ins.InstallCleanUp(model.Id, IOHelper.MapPath(model.TemporaryDirectoryPath)); - var clientDependencyConfig = new Umbraco.Core.Configuration.ClientDependencyConfiguration(Logger); - var clientDependencyUpdated = clientDependencyConfig.IncreaseVersionNumber(); + var clientDependencyConfig = new ClientDependencyConfiguration(Logger); + var clientDependencyUpdated = clientDependencyConfig.UpdateVersionNumber( + UmbracoVersion.SemanticVersion, DateTime.UtcNow, "yyyyMMdd"); //clear the tree cache - we'll do this here even though the browser will reload, but just in case it doesn't can't hurt. //these bits are super old, but cant find another way to do this currently diff --git a/src/Umbraco.Web/Editors/PasswordChanger.cs b/src/Umbraco.Web/Editors/PasswordChanger.cs index 5034ce1950..69cc28ccb6 100644 --- a/src/Umbraco.Web/Editors/PasswordChanger.cs +++ b/src/Umbraco.Web/Editors/PasswordChanger.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Models; +using Umbraco.Web.Security; using IUser = Umbraco.Core.Models.Membership.IUser; namespace Umbraco.Web.Editors diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs index 9c0d46cedb..6a91d20ae0 100644 --- a/src/Umbraco.Web/Editors/PreviewController.cs +++ b/src/Umbraco.Web/Editors/PreviewController.cs @@ -1,10 +1,16 @@ using System; +using System.Web; using System.Web.Mvc; +using System.Web.UI; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Web.Composing; using Umbraco.Web.Features; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.UI.JavaScript; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors { @@ -13,21 +19,22 @@ namespace Umbraco.Web.Editors { private readonly UmbracoFeatures _features; private readonly IGlobalSettings _globalSettings; + private readonly IPublishedSnapshotService _publishedSnapshotService; + private readonly UmbracoContext _umbracoContext; - public PreviewController(UmbracoFeatures features, IGlobalSettings globalSettings) + public PreviewController(UmbracoFeatures features, IGlobalSettings globalSettings, IPublishedSnapshotService publishedSnapshotService, UmbracoContext umbracoContext) { _features = features; _globalSettings = globalSettings; + _publishedSnapshotService = publishedSnapshotService; + _umbracoContext = umbracoContext; } [UmbracoAuthorize(redirectToUmbracoLogin: true)] + [DisableBrowserCache] public ActionResult Index() { - var model = new BackOfficePreview - { - DisableDevicePreview = _features.Disabled.DisableDevicePreview, - PreviewExtendedHeaderView = _features.Enabled.PreviewExtendedView - }; + var model = new BackOfficePreviewModel(_features, _globalSettings); if (model.PreviewExtendedHeaderView.IsNullOrWhiteSpace() == false) { @@ -41,10 +48,45 @@ namespace Umbraco.Web.Editors return View(_globalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + "Index.cshtml", model); } - public ActionResult Editors(string editor) + /// + /// Returns the JavaScript file for preview + /// + /// + [MinifyJavaScriptResult(Order = 0)] + [OutputCache(Order = 1, VaryByParam = "none", Location = OutputCacheLocation.Server, Duration = 5000)] + public JavaScriptResult Application() { - if (string.IsNullOrEmpty(editor)) throw new ArgumentNullException(nameof(editor)); - return View(_globalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + editor.Replace(".html", string.Empty) + ".cshtml"); + var files = JsInitialization.OptimizeScriptFiles(HttpContext, JsInitialization.GetPreviewInitialization()); + var result = JsInitialization.GetJavascriptInitialization(HttpContext, files, "umbraco.preview"); + + return JavaScript(result); } + + /// + /// The endpoint that is loaded within the preview iframe + /// + /// + [UmbracoAuthorize] + public ActionResult Frame(int id, string culture) + { + var user = _umbracoContext.Security.CurrentUser; + + var previewToken = _publishedSnapshotService.EnterPreview(user, id); + + Response.Cookies.Set(new HttpCookie(Constants.Web.PreviewCookieName, previewToken)); + + // use a numeric url because content may not be in cache and so .Url would fail + var query = culture.IsNullOrWhiteSpace() ? string.Empty : $"?culture={culture}"; + Response.Redirect($"../../{id}.aspx{query}", true); + + return null; + } + + ////fixme: not sure we need this anymore since there is no canvas editing - then we can remove that route too + //public ActionResult Editors(string editor) + //{ + // if (string.IsNullOrEmpty(editor)) throw new ArgumentNullException(nameof(editor)); + // return View(_globalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + editor.Replace(".html", string.Empty) + ".cshtml"); + //} } } diff --git a/src/Umbraco.Web/Editors/PublishedStatusController.cs b/src/Umbraco.Web/Editors/PublishedStatusController.cs index 087b96a241..937d3f3137 100644 --- a/src/Umbraco.Web/Editors/PublishedStatusController.cs +++ b/src/Umbraco.Web/Editors/PublishedStatusController.cs @@ -18,13 +18,13 @@ namespace Umbraco.Web.Editors public string GetPublishedStatusUrl() { if (_publishedSnapshotService is PublishedCache.XmlPublishedCache.PublishedSnapshotService) - return "views/dashboard/developer/xmldataintegrityreport.html"; + return "views/dashboard/settings/xmldataintegrityreport.html"; //if (service is PublishedCache.PublishedNoCache.PublishedSnapshotService) // return "views/dashboard/developer/nocache.html"; if (_publishedSnapshotService is PublishedCache.NuCache.PublishedSnapshotService) - return "views/dashboard/developer/nucache.html"; + return "views/dashboard/settings/nucache.html"; throw new NotSupportedException("Not supported: " + _publishedSnapshotService.GetType().FullName); } diff --git a/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs b/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs index d63eaa2b48..377b76f52a 100644 --- a/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs +++ b/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs @@ -12,6 +12,7 @@ using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using File = System.IO.File; +using Umbraco.Core; namespace Umbraco.Web.Editors { @@ -63,7 +64,28 @@ namespace Umbraco.Web.Editors return searchResult; } - + /// + /// This lists the RedirectUrls for a particular content item + /// Do we need to consider paging here? + /// + /// Udi of content item to retrieve RedirectUrls for + /// + [HttpGet] + public RedirectUrlSearchResult RedirectUrlsForContentItem(string contentUdi) + { + var redirectsResult = new RedirectUrlSearchResult(); + if (GuidUdi.TryParse(contentUdi, out var guidIdi)) + { + var redirectUrlService = Services.RedirectUrlService; + var redirects = redirectUrlService.GetContentRedirectUrls(guidIdi.Guid); + redirectsResult.SearchResults = Mapper.Map>(redirects).ToArray(); + //not doing paging 'yet' + redirectsResult.TotalCount = redirects.Count(); + redirectsResult.CurrentPage = 1; + redirectsResult.PageCount = 1; + } + return redirectsResult; + } [HttpPost] public IHttpActionResult DeleteRedirectUrl(Guid id) { diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 28972ba7f6..f7edd3de8f 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -195,6 +195,7 @@ namespace Umbraco.Web.Editors // so to do that here, we'll need to check if this current user is an admin and if not we should exclude all user who are // also admins + var hideDisabledUsers = UmbracoConfig.For.UmbracoSettings().Security.HideDisabledUsersInBackoffice; var excludeUserGroups = new string[0]; var isAdmin = Security.CurrentUser.IsAdmin(); if (isAdmin == false) @@ -217,6 +218,14 @@ namespace Umbraco.Web.Editors filterQuery.Where(x => x.Name.Contains(filter) || x.Username.Contains(filter)); } + if (hideDisabledUsers) + { + if (userStates == null || userStates.Any() == false) + { + userStates = new[] { UserState.Active, UserState.Invited, UserState.LockedOut, UserState.Inactive }; + } + } + long pageIndex = pageNumber - 1; long total; var result = Services.UserService.GetAll(pageIndex, pageSize, out total, orderBy, orderDirection, userStates, userGroups, excludeUserGroups, filterQuery); diff --git a/src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs index b69e5edece..54bbe2022c 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs @@ -69,7 +69,6 @@ namespace Umbraco.Web.HealthCheck.Checks.Permissions { SystemDirectories.Media, PermissionCheckRequirement.Optional }, { SystemDirectories.Scripts, PermissionCheckRequirement.Optional }, { SystemDirectories.Umbraco, PermissionCheckRequirement.Optional }, - { SystemDirectories.UmbracoClient, PermissionCheckRequirement.Optional }, { SystemDirectories.UserControls, PermissionCheckRequirement.Optional }, { SystemDirectories.MvcViews, PermissionCheckRequirement.Optional } }; diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckController.cs b/src/Umbraco.Web/HealthCheck/HealthCheckController.cs index 6412409488..ac64706292 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheckController.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheckController.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.HealthCheck /// /// The API controller used to display the health check info and execute any actions /// - [UmbracoApplicationAuthorize(Core.Constants.Applications.Developer)] + [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] public class HealthCheckController : UmbracoAuthorizedJsonController { private readonly HealthCheckCollection _checks; diff --git a/src/Umbraco.Web/HtmlStringUtilities.cs b/src/Umbraco.Web/HtmlStringUtilities.cs index 26ee4c6557..be14df8e5a 100644 --- a/src/Umbraco.Web/HtmlStringUtilities.cs +++ b/src/Umbraco.Web/HtmlStringUtilities.cs @@ -20,9 +20,9 @@ namespace Umbraco.Web /// /// The text. /// The text with text line breaks replaced with html linebreaks (
)
- public string ReplaceLineBreaksForHtml(string text) + public HtmlString ReplaceLineBreaksForHtml(string text) { - return text.Replace("\r\n", @"
").Replace("\n", @"
").Replace("\r", @"
"); + return new HtmlString(text.Replace("\r\n", @"
").Replace("\n", @"
").Replace("\r", @"
")); } public HtmlString StripHtmlTags(string html, params string[] tags) diff --git a/src/Umbraco.Core/Logging/ImageProcessorLogger.cs b/src/Umbraco.Web/ImageProcessorLogger.cs similarity index 97% rename from src/Umbraco.Core/Logging/ImageProcessorLogger.cs rename to src/Umbraco.Web/ImageProcessorLogger.cs index fa1f117e06..75df6bd246 100644 --- a/src/Umbraco.Core/Logging/ImageProcessorLogger.cs +++ b/src/Umbraco.Web/ImageProcessorLogger.cs @@ -2,8 +2,9 @@ using System.Runtime.CompilerServices; using ImageProcessor.Common.Exceptions; using Umbraco.Core.Composing; +using Umbraco.Core.Logging; -namespace Umbraco.Core.Logging +namespace Umbraco.Web { /// diff --git a/src/Umbraco.Web/Install/Controllers/InstallController.cs b/src/Umbraco.Web/Install/Controllers/InstallController.cs index b886c459ab..175e6543ab 100644 --- a/src/Umbraco.Web/Install/Controllers/InstallController.cs +++ b/src/Umbraco.Web/Install/Controllers/InstallController.cs @@ -6,6 +6,7 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Install; using Umbraco.Web.Security; +using Umbraco.Web.UI.JavaScript; namespace Umbraco.Web.Install.Controllers { diff --git a/src/Umbraco.Web/Install/InstallHelper.cs b/src/Umbraco.Web/Install/InstallHelper.cs index 194fe2aac7..4e9fb27c25 100644 --- a/src/Umbraco.Web/Install/InstallHelper.cs +++ b/src/Umbraco.Web/Install/InstallHelper.cs @@ -19,6 +19,7 @@ namespace Umbraco.Web.Install { public sealed class InstallHelper { + private static HttpClient _httpClient; private readonly DatabaseBuilder _databaseBuilder; private readonly HttpContextBase _httpContext; private readonly ILogger _logger; @@ -161,16 +162,17 @@ namespace Umbraco.Web.Install internal IEnumerable GetStarterKits() { - var packages = new List(); + if (_httpClient == null) + _httpClient = new HttpClient(); + var packages = new List(); try { var requestUri = $"https://our.umbraco.com/webapi/StarterKit/Get/?umbracoVersion={UmbracoVersion.Current}"; using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri)) - using (var httpClient = new HttpClient()) - using (var response = httpClient.SendAsync(request).Result) { + var response = _httpClient.SendAsync(request).Result; packages = response.Content.ReadAsAsync>().Result.ToList(); } } diff --git a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs index 8a67001cc6..0b225e158b 100644 --- a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs @@ -1,13 +1,15 @@ using System; -using System.Collections.Generic; using System.Collections.Specialized; using System.Configuration; +using System.Net; +using System.Net.Http; +using System.Text; using System.Web; using System.Web.Security; +using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Migrations.Install; -using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Web.Install.Models; @@ -27,6 +29,7 @@ namespace Umbraco.Web.Install.InstallSteps private readonly HttpContextBase _http; private readonly IUserService _userService; private readonly DatabaseBuilder _databaseBuilder; + private static HttpClient _httpClient; private readonly IGlobalSettings _globalSettings; public NewInstallStep(HttpContextBase http, IUserService userService, DatabaseBuilder databaseBuilder, IGlobalSettings globalSettings) @@ -79,15 +82,18 @@ namespace Umbraco.Web.Install.InstallSteps admin.Username = user.Email.Trim(); _userService.Save(admin); - - + if (user.SubscribeToNewsLetter) { + if (_httpClient == null) + _httpClient = new HttpClient(); + + var values = new NameValueCollection { { "name", admin.Name }, { "email", admin.Email } }; + var content = new StringContent(JsonConvert.SerializeObject(values), Encoding.UTF8, "application/json"); + try { - var client = new System.Net.WebClient(); - var values = new NameValueCollection { { "name", admin.Name }, { "email", admin.Email} }; - client.UploadValues("https://shop.umbraco.com/base/Ecom/SubmitEmail/installer.aspx", values); + var response = _httpClient.PostAsync("https://shop.umbraco.com/base/Ecom/SubmitEmail/installer.aspx", content).Result; } catch { /* fail in silence */ } } @@ -114,11 +120,14 @@ namespace Umbraco.Web.Install.InstallSteps public override string View { - get { return RequiresExecution(null) - //the user UI + get + { + return RequiresExecution(null) + //the user UI ? "user" - //the continue install UI - : "continueinstall"; } + //the continue install UI + : "continueinstall"; + } } public override bool RequiresExecution(UserModel model) diff --git a/src/Umbraco.Core/Macros/MacroTagParser.cs b/src/Umbraco.Web/Macros/MacroTagParser.cs similarity index 99% rename from src/Umbraco.Core/Macros/MacroTagParser.cs rename to src/Umbraco.Web/Macros/MacroTagParser.cs index 469b2ed4d0..cbfc1ce0f7 100644 --- a/src/Umbraco.Core/Macros/MacroTagParser.cs +++ b/src/Umbraco.Web/Macros/MacroTagParser.cs @@ -5,7 +5,7 @@ using System.Text.RegularExpressions; using HtmlAgilityPack; using Umbraco.Core.Xml; -namespace Umbraco.Core.Macros +namespace Umbraco.Web.Macros { /// /// Parses the macro syntax in a string and renders out it's contents diff --git a/src/Umbraco.Web/Macros/PartialViewMacroPage.cs b/src/Umbraco.Web/Macros/PartialViewMacroPage.cs index 98b57272f7..7297fec6d3 100644 --- a/src/Umbraco.Web/Macros/PartialViewMacroPage.cs +++ b/src/Umbraco.Web/Macros/PartialViewMacroPage.cs @@ -1,6 +1,4 @@ -using System.Web.Mvc; -using Umbraco.Core.Models; -using Umbraco.Web.Models; +using Umbraco.Web.Models; using Umbraco.Web.Mvc; namespace Umbraco.Web.Macros diff --git a/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs b/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs index cec9de7404..921036c75a 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs @@ -12,18 +12,20 @@ namespace Umbraco.Web.Media.EmbedProviders { //TODO: Make all Http calls async - public abstract class AbstractOEmbedProvider: IEmbedProvider + public abstract class AbstractOEmbedProvider : IEmbedProvider { + private static HttpClient _httpClient; + public virtual bool SupportsDimensions { get { return true; } } [ProviderSetting] - public string APIEndpoint{ get;set; } + public string APIEndpoint { get; set; } [ProviderSetting] - public Dictionary RequestParams{ get;set; } + public Dictionary RequestParams { get; set; } public abstract string GetMarkup(string url, int maxWidth, int maxHeight); @@ -51,9 +53,13 @@ namespace Umbraco.Web.Media.EmbedProviders public virtual string DownloadResponse(string url) { - using (var webClient = new WebClient()) + if (_httpClient == null) + _httpClient = new HttpClient(); + + using (var request = new HttpRequestMessage(HttpMethod.Get, url)) { - return webClient.DownloadString(url); + var response = _httpClient.SendAsync(request).Result; + return response.Content.ReadAsStringAsync().Result; } } diff --git a/src/Umbraco.Core/Media/Exif/BitConverterEx.cs b/src/Umbraco.Web/Media/Exif/BitConverterEx.cs similarity index 99% rename from src/Umbraco.Core/Media/Exif/BitConverterEx.cs rename to src/Umbraco.Web/Media/Exif/BitConverterEx.cs index 9447b057b1..9850efa907 100644 --- a/src/Umbraco.Core/Media/Exif/BitConverterEx.cs +++ b/src/Umbraco.Web/Media/Exif/BitConverterEx.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// An endian-aware converter for converting between base data types diff --git a/src/Umbraco.Core/Media/Exif/ExifBitConverter.cs b/src/Umbraco.Web/Media/Exif/ExifBitConverter.cs similarity index 99% rename from src/Umbraco.Core/Media/Exif/ExifBitConverter.cs rename to src/Umbraco.Web/Media/Exif/ExifBitConverter.cs index 6247929cf6..e116e1994f 100644 --- a/src/Umbraco.Core/Media/Exif/ExifBitConverter.cs +++ b/src/Umbraco.Web/Media/Exif/ExifBitConverter.cs @@ -1,7 +1,7 @@ using System; using System.Text; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Converts between exif data types and array of bytes. diff --git a/src/Umbraco.Core/Media/Exif/ExifEnums.cs b/src/Umbraco.Web/Media/Exif/ExifEnums.cs similarity index 99% rename from src/Umbraco.Core/Media/Exif/ExifEnums.cs rename to src/Umbraco.Web/Media/Exif/ExifEnums.cs index 2c2f9f798d..5ba5b1d704 100644 --- a/src/Umbraco.Core/Media/Exif/ExifEnums.cs +++ b/src/Umbraco.Web/Media/Exif/ExifEnums.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { internal enum Compression : ushort { diff --git a/src/Umbraco.Core/Media/Exif/ExifExceptions.cs b/src/Umbraco.Web/Media/Exif/ExifExceptions.cs similarity index 96% rename from src/Umbraco.Core/Media/Exif/ExifExceptions.cs rename to src/Umbraco.Web/Media/Exif/ExifExceptions.cs index b0301e951a..040e84ff99 100644 --- a/src/Umbraco.Core/Media/Exif/ExifExceptions.cs +++ b/src/Umbraco.Web/Media/Exif/ExifExceptions.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// The exception that is thrown when the format of the JPEG/Exif file diff --git a/src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs b/src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs similarity index 99% rename from src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs rename to src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs index 2a3ed38928..40ba275fe4 100644 --- a/src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs +++ b/src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs @@ -1,7 +1,7 @@ using System; using System.Text; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents an enumerated value. diff --git a/src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs b/src/Umbraco.Web/Media/Exif/ExifFileTypeDescriptor.cs similarity index 99% rename from src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs rename to src/Umbraco.Web/Media/Exif/ExifFileTypeDescriptor.cs index 00fd9a11c0..4eba0e5686 100644 --- a/src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs +++ b/src/Umbraco.Web/Media/Exif/ExifFileTypeDescriptor.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Provides a custom type descriptor for an ExifFile instance. diff --git a/src/Umbraco.Core/Media/Exif/ExifInterOperability.cs b/src/Umbraco.Web/Media/Exif/ExifInterOperability.cs similarity index 98% rename from src/Umbraco.Core/Media/Exif/ExifInterOperability.cs rename to src/Umbraco.Web/Media/Exif/ExifInterOperability.cs index b43177d49c..e7d8813767 100644 --- a/src/Umbraco.Core/Media/Exif/ExifInterOperability.cs +++ b/src/Umbraco.Web/Media/Exif/ExifInterOperability.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents interoperability data for an exif tag in the platform byte order. diff --git a/src/Umbraco.Core/Media/Exif/ExifProperty.cs b/src/Umbraco.Web/Media/Exif/ExifProperty.cs similarity index 99% rename from src/Umbraco.Core/Media/Exif/ExifProperty.cs rename to src/Umbraco.Web/Media/Exif/ExifProperty.cs index b05d88eb0a..3a6efcab0b 100644 --- a/src/Umbraco.Core/Media/Exif/ExifProperty.cs +++ b/src/Umbraco.Web/Media/Exif/ExifProperty.cs @@ -1,7 +1,7 @@ using System; using System.Text; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents the abstract base class for an Exif property. diff --git a/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs b/src/Umbraco.Web/Media/Exif/ExifPropertyCollection.cs similarity index 99% rename from src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs rename to src/Umbraco.Web/Media/Exif/ExifPropertyCollection.cs index 233fe27a91..7f6258cbca 100644 --- a/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs +++ b/src/Umbraco.Web/Media/Exif/ExifPropertyCollection.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents a collection of objects. diff --git a/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs b/src/Umbraco.Web/Media/Exif/ExifPropertyFactory.cs similarity index 99% rename from src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs rename to src/Umbraco.Web/Media/Exif/ExifPropertyFactory.cs index 69efb809fc..08d1f40afd 100644 --- a/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs +++ b/src/Umbraco.Web/Media/Exif/ExifPropertyFactory.cs @@ -1,7 +1,7 @@ using System; using System.Text; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Creates exif properties from interoperability parameters. diff --git a/src/Umbraco.Core/Media/Exif/ExifTag.cs b/src/Umbraco.Web/Media/Exif/ExifTag.cs similarity index 99% rename from src/Umbraco.Core/Media/Exif/ExifTag.cs rename to src/Umbraco.Web/Media/Exif/ExifTag.cs index 47facc28b8..a65d75d7c7 100644 --- a/src/Umbraco.Core/Media/Exif/ExifTag.cs +++ b/src/Umbraco.Web/Media/Exif/ExifTag.cs @@ -1,5 +1,5 @@  -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents the tags associated with exif fields. diff --git a/src/Umbraco.Core/Media/Exif/ExifTagFactory.cs b/src/Umbraco.Web/Media/Exif/ExifTagFactory.cs similarity index 98% rename from src/Umbraco.Core/Media/Exif/ExifTagFactory.cs rename to src/Umbraco.Web/Media/Exif/ExifTagFactory.cs index 729ce9c345..a9f1896fe7 100644 --- a/src/Umbraco.Core/Media/Exif/ExifTagFactory.cs +++ b/src/Umbraco.Web/Media/Exif/ExifTagFactory.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { internal static class ExifTagFactory { diff --git a/src/Umbraco.Core/Media/Exif/IFD.cs b/src/Umbraco.Web/Media/Exif/IFD.cs similarity index 90% rename from src/Umbraco.Core/Media/Exif/IFD.cs rename to src/Umbraco.Web/Media/Exif/IFD.cs index 68628e21d8..c73a7ed97a 100644 --- a/src/Umbraco.Core/Media/Exif/IFD.cs +++ b/src/Umbraco.Web/Media/Exif/IFD.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents the IFD section containing tags. diff --git a/src/Umbraco.Core/Media/Exif/ImageFile.cs b/src/Umbraco.Web/Media/Exif/ImageFile.cs similarity index 88% rename from src/Umbraco.Core/Media/Exif/ImageFile.cs rename to src/Umbraco.Web/Media/Exif/ImageFile.cs index 24eb780c9d..d0c3ef7411 100644 --- a/src/Umbraco.Core/Media/Exif/ImageFile.cs +++ b/src/Umbraco.Web/Media/Exif/ImageFile.cs @@ -2,8 +2,9 @@ using System.Drawing; using System.IO; using System.Text; +using Umbraco.Web.Media.TypeDetector; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents the base class for image files. @@ -118,22 +119,25 @@ namespace Umbraco.Core.Media.Exif /// The created from the file. public static ImageFile FromStream(Stream stream, Encoding encoding) { - stream.Seek (0, SeekOrigin.Begin); - byte[] header = new byte[8]; - stream.Seek (0, SeekOrigin.Begin); - if (stream.Read (header, 0, header.Length) != header.Length) - throw new NotValidImageFileException (); - // JPEG - if (header[0] == 0xFF && header[1] == 0xD8) - return new JPEGFile (stream, encoding); + if (JpegDetector.IsOfType(stream)) + { + return new JPEGFile(stream, encoding); + } // TIFF - string tiffHeader = Encoding.ASCII.GetString (header, 0, 4); - if (tiffHeader == "MM\x00\x2a" || tiffHeader == "II\x2a\x00") - return new TIFFFile (stream, encoding); + if (TIFFDetector.IsOfType(stream)) + { + return new TIFFFile(stream, encoding); + } - throw new NotValidImageFileException (); + // SVG + if (SvgDetector.IsOfType(stream)) + { + return new SvgFile(stream); + } + + throw new NotValidImageFileException(); } #endregion } diff --git a/src/Umbraco.Core/Media/Exif/ImageFileDirectory.cs b/src/Umbraco.Web/Media/Exif/ImageFileDirectory.cs similarity index 99% rename from src/Umbraco.Core/Media/Exif/ImageFileDirectory.cs rename to src/Umbraco.Web/Media/Exif/ImageFileDirectory.cs index 898e0fd017..7b2c134f52 100644 --- a/src/Umbraco.Core/Media/Exif/ImageFileDirectory.cs +++ b/src/Umbraco.Web/Media/Exif/ImageFileDirectory.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents an image file directory. diff --git a/src/Umbraco.Core/Media/Exif/ImageFileDirectoryEntry.cs b/src/Umbraco.Web/Media/Exif/ImageFileDirectoryEntry.cs similarity index 99% rename from src/Umbraco.Core/Media/Exif/ImageFileDirectoryEntry.cs rename to src/Umbraco.Web/Media/Exif/ImageFileDirectoryEntry.cs index e4be3863f0..2d364c4d0c 100644 --- a/src/Umbraco.Core/Media/Exif/ImageFileDirectoryEntry.cs +++ b/src/Umbraco.Web/Media/Exif/ImageFileDirectoryEntry.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents an entry in the image file directory. diff --git a/src/Umbraco.Core/Media/Exif/ImageFileFormat.cs b/src/Umbraco.Web/Media/Exif/ImageFileFormat.cs similarity index 78% rename from src/Umbraco.Core/Media/Exif/ImageFileFormat.cs rename to src/Umbraco.Web/Media/Exif/ImageFileFormat.cs index 8e1433c947..364877f7da 100644 --- a/src/Umbraco.Core/Media/Exif/ImageFileFormat.cs +++ b/src/Umbraco.Web/Media/Exif/ImageFileFormat.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents the format of the . @@ -17,5 +17,9 @@ /// The file is a TIFF File. /// TIFF, + /// + /// The file is a SVG File. + /// + SVG, } } diff --git a/src/Umbraco.Core/Media/Exif/JFIFEnums.cs b/src/Umbraco.Web/Media/Exif/JFIFEnums.cs similarity index 96% rename from src/Umbraco.Core/Media/Exif/JFIFEnums.cs rename to src/Umbraco.Web/Media/Exif/JFIFEnums.cs index 6cd7089727..d3a55eec79 100644 --- a/src/Umbraco.Core/Media/Exif/JFIFEnums.cs +++ b/src/Umbraco.Web/Media/Exif/JFIFEnums.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents the units for the X and Y densities diff --git a/src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs b/src/Umbraco.Web/Media/Exif/JFIFExtendedProperty.cs similarity index 98% rename from src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs rename to src/Umbraco.Web/Media/Exif/JFIFExtendedProperty.cs index 573eedf146..94b255f4d1 100644 --- a/src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs +++ b/src/Umbraco.Web/Media/Exif/JFIFExtendedProperty.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents the JFIF version as a 16 bit unsigned integer. (EXIF Specification: SHORT) diff --git a/src/Umbraco.Core/Media/Exif/JFIFThumbnail.cs b/src/Umbraco.Web/Media/Exif/JFIFThumbnail.cs similarity index 97% rename from src/Umbraco.Core/Media/Exif/JFIFThumbnail.cs rename to src/Umbraco.Web/Media/Exif/JFIFThumbnail.cs index 95987ea79a..e7fc5fd51a 100644 --- a/src/Umbraco.Core/Media/Exif/JFIFThumbnail.cs +++ b/src/Umbraco.Web/Media/Exif/JFIFThumbnail.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents a JFIF thumbnail. diff --git a/src/Umbraco.Core/Media/Exif/JPEGExceptions.cs b/src/Umbraco.Web/Media/Exif/JPEGExceptions.cs similarity index 98% rename from src/Umbraco.Core/Media/Exif/JPEGExceptions.cs rename to src/Umbraco.Web/Media/Exif/JPEGExceptions.cs index 40b62b25cc..631c3cb1b9 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGExceptions.cs +++ b/src/Umbraco.Web/Media/Exif/JPEGExceptions.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// diff --git a/src/Umbraco.Core/Media/Exif/JPEGFile.cs b/src/Umbraco.Web/Media/Exif/JPEGFile.cs similarity index 99% rename from src/Umbraco.Core/Media/Exif/JPEGFile.cs rename to src/Umbraco.Web/Media/Exif/JPEGFile.cs index 032668dc0a..d7a5d322dc 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGFile.cs +++ b/src/Umbraco.Web/Media/Exif/JPEGFile.cs @@ -4,7 +4,7 @@ using System.Drawing; using System.IO; using System.Text; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents the binary view of a JPEG compressed file. diff --git a/src/Umbraco.Core/Media/Exif/JPEGMarker.cs b/src/Umbraco.Web/Media/Exif/JPEGMarker.cs similarity index 98% rename from src/Umbraco.Core/Media/Exif/JPEGMarker.cs rename to src/Umbraco.Web/Media/Exif/JPEGMarker.cs index ac65023976..3fb04dff28 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGMarker.cs +++ b/src/Umbraco.Web/Media/Exif/JPEGMarker.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents a JPEG marker byte. diff --git a/src/Umbraco.Core/Media/Exif/JPEGSection.cs b/src/Umbraco.Web/Media/Exif/JPEGSection.cs similarity index 98% rename from src/Umbraco.Core/Media/Exif/JPEGSection.cs rename to src/Umbraco.Web/Media/Exif/JPEGSection.cs index 5062f05491..a1bc420fe4 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGSection.cs +++ b/src/Umbraco.Web/Media/Exif/JPEGSection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents the memory view of a JPEG section. diff --git a/src/Umbraco.Core/Media/Exif/MathEx.cs b/src/Umbraco.Web/Media/Exif/MathEx.cs similarity index 99% rename from src/Umbraco.Core/Media/Exif/MathEx.cs rename to src/Umbraco.Web/Media/Exif/MathEx.cs index 5364d18d0f..508c5025f7 100644 --- a/src/Umbraco.Core/Media/Exif/MathEx.cs +++ b/src/Umbraco.Web/Media/Exif/MathEx.cs @@ -1,7 +1,7 @@ using System; using System.Text; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Contains extended Math functions. diff --git a/src/Umbraco.Web/Media/Exif/SvgFile.cs b/src/Umbraco.Web/Media/Exif/SvgFile.cs new file mode 100644 index 0000000000..8916ac0801 --- /dev/null +++ b/src/Umbraco.Web/Media/Exif/SvgFile.cs @@ -0,0 +1,37 @@ +using System; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +namespace Umbraco.Web.Media.Exif +{ + internal class SvgFile : ImageFile + { + public SvgFile(Stream fileStream) + { + fileStream.Position = 0; + + var document = XDocument.Load(fileStream); //if it throws an exception the ugly try catch in MediaFileSystem will catch it + + var width = document.Root?.Attributes().Where(x => x.Name == "width").Select(x => x.Value).FirstOrDefault(); + var height = document.Root?.Attributes().Where(x => x.Name == "height").Select(x => x.Value).FirstOrDefault(); + + Properties.Add(new ExifSInt(ExifTag.PixelYDimension, + height == null ? Core.Constants.Conventions.Media.DefaultSize : int.Parse(height))); + Properties.Add(new ExifSInt(ExifTag.PixelXDimension, + width == null ? Core.Constants.Conventions.Media.DefaultSize : int.Parse(width))); + + Format = ImageFileFormat.SVG; + } + + public override void Save(Stream stream) + { + } + + public override Image ToImage() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Umbraco.Core/Media/Exif/TIFFFile.cs b/src/Umbraco.Web/Media/Exif/TIFFFile.cs similarity index 99% rename from src/Umbraco.Core/Media/Exif/TIFFFile.cs rename to src/Umbraco.Web/Media/Exif/TIFFFile.cs index 5988a37b18..4f5301d526 100644 --- a/src/Umbraco.Core/Media/Exif/TIFFFile.cs +++ b/src/Umbraco.Web/Media/Exif/TIFFFile.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Drawing; using System.IO; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents the binary view of a TIFF file. diff --git a/src/Umbraco.Core/Media/Exif/TIFFHeader.cs b/src/Umbraco.Web/Media/Exif/TIFFHeader.cs similarity index 98% rename from src/Umbraco.Core/Media/Exif/TIFFHeader.cs rename to src/Umbraco.Web/Media/Exif/TIFFHeader.cs index 89b0c02c2e..339525ef2c 100644 --- a/src/Umbraco.Core/Media/Exif/TIFFHeader.cs +++ b/src/Umbraco.Web/Media/Exif/TIFFHeader.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents a TIFF Header. diff --git a/src/Umbraco.Core/Media/Exif/TIFFStrip.cs b/src/Umbraco.Web/Media/Exif/TIFFStrip.cs similarity index 96% rename from src/Umbraco.Core/Media/Exif/TIFFStrip.cs rename to src/Umbraco.Web/Media/Exif/TIFFStrip.cs index 52ca4e5034..8207eb64e8 100644 --- a/src/Umbraco.Core/Media/Exif/TIFFStrip.cs +++ b/src/Umbraco.Web/Media/Exif/TIFFStrip.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Represents a strip of compressed image data in a TIFF file. diff --git a/src/Umbraco.Core/Media/Exif/Utility.cs b/src/Umbraco.Web/Media/Exif/Utility.cs similarity index 96% rename from src/Umbraco.Core/Media/Exif/Utility.cs rename to src/Umbraco.Web/Media/Exif/Utility.cs index 2d7a304c3e..d2daa3b632 100644 --- a/src/Umbraco.Core/Media/Exif/Utility.cs +++ b/src/Umbraco.Web/Media/Exif/Utility.cs @@ -1,6 +1,6 @@ using System.IO; -namespace Umbraco.Core.Media.Exif +namespace Umbraco.Web.Media.Exif { /// /// Contains utility functions. diff --git a/src/Umbraco.Web/Media/ImageHelper.cs b/src/Umbraco.Web/Media/ImageHelper.cs new file mode 100644 index 0000000000..3fbc1e060a --- /dev/null +++ b/src/Umbraco.Web/Media/ImageHelper.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Media; +using Umbraco.Core.Models; +using Umbraco.Web.Composing; +using Umbraco.Web.Media.Exif; + +namespace Umbraco.Web.Media +{ + public static class ImageHelper + { + /// + /// Gets the dimensions of an image. + /// + /// A stream containing the image bytes. + /// The dimension of the image. + /// First try with EXIF as it is faster and does not load the entire image + /// in memory. Fallback to GDI which means loading the image in memory and thus + /// use potentially large amounts of memory. + public static Size GetDimensions(Stream stream) + { + try + { + //Try to load with exif + var jpgInfo = ImageFile.FromStream(stream); + + if (jpgInfo.Format != ImageFileFormat.Unknown + && jpgInfo.Properties.ContainsKey(ExifTag.PixelYDimension) + && jpgInfo.Properties.ContainsKey(ExifTag.PixelXDimension)) + { + var height = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelYDimension].Value); + var width = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelXDimension].Value); + if (height > 0 && width > 0) + { + return new Size(width, height); + } + } + + //we have no choice but to try to read in via GDI + using (var image = Image.FromStream(stream)) + { + + var fileWidth = image.Width; + var fileHeight = image.Height; + return new Size(fileWidth, fileHeight); + } + } + catch (Exception) + { + //We will just swallow, just means we can't read exif data, we don't want to log an error either + return new Size(Constants.Conventions.Media.DefaultSize, Constants.Conventions.Media.DefaultSize); + } + + } + + + + } +} diff --git a/src/Umbraco.Web/Media/TypeDetector/JpegDetector.cs b/src/Umbraco.Web/Media/TypeDetector/JpegDetector.cs new file mode 100644 index 0000000000..287709f9c6 --- /dev/null +++ b/src/Umbraco.Web/Media/TypeDetector/JpegDetector.cs @@ -0,0 +1,14 @@ +using System.IO; + +namespace Umbraco.Web.Media.TypeDetector +{ + public class JpegDetector : RasterizedTypeDetector + { + public static bool IsOfType(Stream fileStream) + { + var header = GetFileHeader(fileStream); + + return header[0] == 0xff && header[1] == 0xD8; + } + } +} diff --git a/src/Umbraco.Web/Media/TypeDetector/RasterizedTypeDetector.cs b/src/Umbraco.Web/Media/TypeDetector/RasterizedTypeDetector.cs new file mode 100644 index 0000000000..4bb074e5a2 --- /dev/null +++ b/src/Umbraco.Web/Media/TypeDetector/RasterizedTypeDetector.cs @@ -0,0 +1,16 @@ +using System.IO; + +namespace Umbraco.Web.Media.TypeDetector +{ + public abstract class RasterizedTypeDetector + { + public static byte[] GetFileHeader(Stream fileStream) + { + fileStream.Seek(0, SeekOrigin.Begin); + byte[] header = new byte[8]; + fileStream.Seek(0, SeekOrigin.Begin); + + return header; + } + } +} diff --git a/src/Umbraco.Web/Media/TypeDetector/SvgDetector.cs b/src/Umbraco.Web/Media/TypeDetector/SvgDetector.cs new file mode 100644 index 0000000000..621e2237bc --- /dev/null +++ b/src/Umbraco.Web/Media/TypeDetector/SvgDetector.cs @@ -0,0 +1,24 @@ +using System.IO; +using System.Xml.Linq; + +namespace Umbraco.Web.Media.TypeDetector +{ + public class SvgDetector + { + public static bool IsOfType(Stream fileStream) + { + var document = new XDocument(); + + try + { + document = XDocument.Load(fileStream); + } + catch (System.Exception ex) + { + return false; + } + + return document.Root?.Name.LocalName == "svg"; + } + } +} diff --git a/src/Umbraco.Web/Media/TypeDetector/TIFFDetector.cs b/src/Umbraco.Web/Media/TypeDetector/TIFFDetector.cs new file mode 100644 index 0000000000..08126136b8 --- /dev/null +++ b/src/Umbraco.Web/Media/TypeDetector/TIFFDetector.cs @@ -0,0 +1,24 @@ +using System.IO; +using System.Text; + +namespace Umbraco.Web.Media.TypeDetector +{ + public class TIFFDetector + { + public static bool IsOfType(Stream fileStream) + { + string tiffHeader = GetFileHeader(fileStream); + + return tiffHeader == "MM\x00\x2a" || tiffHeader == "II\x2a\x00"; + } + + public static string GetFileHeader(Stream fileStream) + { + var header = RasterizedTypeDetector.GetFileHeader(fileStream); + + string tiffHeader = Encoding.ASCII.GetString(header, 0, 4); + + return tiffHeader; + } + } +} diff --git a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs b/src/Umbraco.Web/Media/UploadAutoFillProperties.cs similarity index 60% rename from src/Umbraco.Core/Media/UploadAutoFillProperties.cs rename to src/Umbraco.Web/Media/UploadAutoFillProperties.cs index 2045e947ac..6a56dec918 100644 --- a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs +++ b/src/Umbraco.Web/Media/UploadAutoFillProperties.cs @@ -2,58 +2,28 @@ using System.Drawing; using System.IO; using System.Linq; +using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; -namespace Umbraco.Core.Media +namespace Umbraco.Web.Media { /// /// Provides methods to manage auto-fill properties for upload fields. /// internal class UploadAutoFillProperties { - private readonly ILogger _logger; private readonly MediaFileSystem _mediaFileSystem; - private readonly IContentSection _contentSettings; + private readonly ILogger _logger; + private readonly IContentSection _contentSection; - public UploadAutoFillProperties(MediaFileSystem mediaFileSystem, ILogger logger, IContentSection contentSettings) + public UploadAutoFillProperties(MediaFileSystem mediaFileSystem, ILogger logger, IContentSection contentSection) { - _mediaFileSystem = mediaFileSystem; - _logger = logger; - _contentSettings = contentSettings; - } - - /// - /// Gets the auto-fill configuration for a specified property alias. - /// - /// The property type alias. - /// The auto-fill configuration for the specified property alias, or null. - public IImagingAutoFillUploadField GetConfig(string propertyTypeAlias) - { - var autoFillConfigs = _contentSettings.ImageAutoFillProperties; - return autoFillConfigs?.FirstOrDefault(x => x.Alias == propertyTypeAlias); - } - - /// - /// Resets the auto-fill properties of a content item, for a specified property alias. - /// - /// The content item. - /// The property type alias. - /// Variation language. - /// Variation segment. - public void Reset(IContentBase content, string propertyTypeAlias, string culture, string segment) - { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (propertyTypeAlias == null) throw new ArgumentNullException(nameof(propertyTypeAlias)); - - // get the config, no config = nothing to do - var autoFillConfig = GetConfig(propertyTypeAlias); - if (autoFillConfig == null) return; // nothing - - // reset - Reset(content, autoFillConfig, culture, segment); + _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _contentSection = contentSection ?? throw new ArgumentNullException(nameof(contentSection)); } /// @@ -70,56 +40,7 @@ namespace Umbraco.Core.Media ResetProperties(content, autoFillConfig, culture, segment); } - - /// - /// Populates the auto-fill properties of a content item. - /// - /// The content item. - /// The property type alias. - /// The filesystem-relative filepath, or null to clear properties. - /// Variation language. - /// Variation segment. - public void Populate(IContentBase content, string propertyTypeAlias, string filepath, string culture, string segment) - { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (propertyTypeAlias == null) throw new ArgumentNullException(nameof(propertyTypeAlias)); - - // no property = nothing to do - if (content.Properties.Contains(propertyTypeAlias) == false) return; - - // get the config, no config = nothing to do - var autoFillConfig = GetConfig(propertyTypeAlias); - if (autoFillConfig == null) return; // nothing - - // populate - Populate(content, autoFillConfig, filepath, culture, segment); - } - - /// - /// Populates the auto-fill properties of a content item. - /// - /// The content item. - /// The property type alias. - /// The filesystem-relative filepath, or null to clear properties. - /// The stream containing the file data. - /// Variation language. - /// Variation segment. - public void Populate(IContentBase content, string propertyTypeAlias, string filepath, Stream filestream, string culture, string segment) - { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (propertyTypeAlias == null) throw new ArgumentNullException(nameof(propertyTypeAlias)); - - // no property = nothing to do - if (content.Properties.Contains(propertyTypeAlias) == false) return; - - // get the config, no config = nothing to do - var autoFillConfig = GetConfig(propertyTypeAlias); - if (autoFillConfig == null) return; // nothing - - // populate - Populate(content, autoFillConfig, filepath, filestream, culture, segment); - } - + /// /// Populates the auto-fill properties of a content item, for a specified auto-fill configuration. /// @@ -147,7 +68,7 @@ namespace Umbraco.Core.Media using (var filestream = _mediaFileSystem.OpenFile(filepath)) { var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); - var size = _mediaFileSystem.IsImageFile(extension) ? (Size?) _mediaFileSystem.GetDimensions(filestream) : null; + var size = _contentSection.IsImageFile(extension) ? (Size?)ImageHelper.GetDimensions(filestream) : null; SetProperties(content, autoFillConfig, size, filestream.Length, extension, culture, segment); } } @@ -181,7 +102,7 @@ namespace Umbraco.Core.Media else { var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); - var size = _mediaFileSystem.IsImageFile(extension) ? (Size?)_mediaFileSystem.GetDimensions(filestream) : null; + var size = _contentSection.IsImageFile(extension) ? (Size?)ImageHelper.GetDimensions(filestream) : null; SetProperties(content, autoFillConfig, size, filestream.Length, extension, culture, segment); } } diff --git a/src/Umbraco.Web/ModelStateExtensions.cs b/src/Umbraco.Web/ModelStateExtensions.cs index 012728ef5a..48eb06c88a 100644 --- a/src/Umbraco.Web/ModelStateExtensions.cs +++ b/src/Umbraco.Web/ModelStateExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web.Mvc; +using Umbraco.Core; namespace Umbraco.Web { @@ -37,20 +38,7 @@ namespace Umbraco.Web { return state.Where(v => v.Key.StartsWith(prefix + ".")).All(v => !v.Value.Errors.Any()); } - - - //NOTE: we used this alot in v5 when we had editors in MVC, this was really handy for knockout editors using JS - - ///// - ///// Adds an error to the model state that has to do with data validation, this is generally used for JSON responses - ///// - ///// - ///// - //public static void AddDataValidationError(this ModelStateDictionary state, string errorMessage) - //{ - // state.AddModelError("DataValidation", errorMessage); - //} - + /// /// Adds the error to model state correctly for a property so we can use it on the client side. /// @@ -66,6 +54,25 @@ namespace Umbraco.Web modelState.AddValidationError(result, "_Properties", propertyAlias, culture); } + /// + /// Returns a list of cultures that have property errors + /// + /// + /// + internal static IReadOnlyList GetCulturesWithPropertyErrors(this System.Web.Http.ModelBinding.ModelStateDictionary modelState) + { + //Add any culture specific errors here + var cultureErrors = modelState.Keys + .Select(x => x.Split('.')) //split into parts + .Where(x => x.Length >= 3 && x[0] == "_Properties") //only choose _Properties errors + .Select(x => x[2]) //select the culture part + .Where(x => !x.IsNullOrWhiteSpace()) //if it has a value + .Distinct() + .ToList(); + + return cultureErrors; + } + /// /// Adds the error to model state correctly for a property so we can use it on the client side. /// diff --git a/src/Umbraco.Web/Models/ContentEditing/BackOfficePreview.cs b/src/Umbraco.Web/Models/ContentEditing/BackOfficePreview.cs deleted file mode 100644 index f8633acdd0..0000000000 --- a/src/Umbraco.Web/Models/ContentEditing/BackOfficePreview.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Umbraco.Web.Models.ContentEditing -{ - /// - /// The model representing Previewing of a content item from the back office - /// - public class BackOfficePreview - { - public string PreviewExtendedHeaderView { get; set; } - - //TODO: We could potentially have a 'footer' view - public bool DisableDevicePreview { get; set; } - } -} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentApp.cs b/src/Umbraco.Web/Models/ContentEditing/ContentApp.cs deleted file mode 100644 index f95d6ac6fd..0000000000 --- a/src/Umbraco.Web/Models/ContentEditing/ContentApp.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Runtime.Serialization; - -namespace Umbraco.Web.Models.ContentEditing -{ - /// - /// Defines a "Content App" which are editor extensions - /// - [DataContract(Name = "app", Namespace = "")] - public class ContentApp - { - [DataMember(Name = "name")] - public string Name { get; set; } - - [DataMember(Name = "alias")] - public string Alias { get; set; } - - [DataMember(Name = "icon")] - public string Icon { get; set; } - - [DataMember(Name = "view")] - public string View { get; set; } - - /// - /// The view model specific to this app - /// - [DataMember(Name = "viewModel")] - public object ViewModel { get; set; } - - /// - /// Normally reserved for Angular to deal with but in some cases this can be set on the server side - /// - [DataMember(Name = "active")] - public bool Active { get; set; } - } -} - diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentDomainsAndCulture.cs b/src/Umbraco.Web/Models/ContentEditing/ContentDomainsAndCulture.cs new file mode 100644 index 0000000000..9f0750cb7c --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/ContentDomainsAndCulture.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Umbraco.Web.Models.ContentEditing +{ + public class ContentDomainsAndCulture + { + public IEnumerable Domains { get; set; } + + public string Language { get; internal set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs index 427dadb2c9..16eb3b9e87 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs @@ -4,9 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; using Newtonsoft.Json; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Validation; -using Umbraco.Web.WebApi; +using Newtonsoft.Json.Converters; namespace Umbraco.Web.Models.ContentEditing { @@ -22,8 +20,11 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "createDate")] public DateTime CreateDate { get; set; } + /// + /// Boolean indicating if this item is published or not based on it's + /// [DataMember(Name = "published")] - public bool Published { get; set; } + public bool Published => State == ContentSavedState.Published || State == ContentSavedState.PublishedPendingChanges; /// /// Determines if the content item is a draft @@ -44,6 +45,16 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } + /// + /// The saved/published state of an item + /// + /// + /// This is nullable since it's only relevant for content (non-content like media + members will be null) + /// + [DataMember(Name = "state")] + [JsonConverter(typeof(StringEnumConverter))] + public ContentSavedState? State { get; set; } + protected bool Equals(ContentItemBasic other) { return Id == other.Id; diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs index 750fdf5925..a729d51d13 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs @@ -6,6 +6,7 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Serialization; using Umbraco.Web.Routing; diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentSavedState.cs b/src/Umbraco.Web/Models/ContentEditing/ContentSavedState.cs index f433af58d6..2ceb682a13 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentSavedState.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentSavedState.cs @@ -8,21 +8,21 @@ /// /// The item isn't created yet /// - NotCreated, + NotCreated = 1, /// /// The item is saved but isn't published /// - Draft, + Draft = 2, /// /// The item is published and there are no pending changes /// - Published, + Published = 3, /// /// The item is published and there are pending changes /// - PublishedPendingChanges + PublishedPendingChanges = 4 } } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs index 3078fe97da..90da6d6d04 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; @@ -12,11 +13,12 @@ namespace Umbraco.Web.Models.ContentEditing /// Represents the variant info for a content item /// [DataContract(Name = "contentVariant", Namespace = "")] - public class ContentVariantDisplay : ITabbedContent, IContentProperties + public class ContentVariantDisplay : ITabbedContent, IContentProperties, INotificationModel { public ContentVariantDisplay() { Tabs = new List>(); + Notifications = new List(); } [DataMember(Name = "name", IsRequired = true)] @@ -59,6 +61,16 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "publishDate")] public DateTime? PublishDate { get; set; } - + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + /// + /// The notifications assigned to a variant are currently only used to show custom messagse in the save/publish dialogs. + /// + [DataMember(Name = "notifications")] + [ReadOnly(true)] + public List Notifications { get; private set; } + } } diff --git a/src/Umbraco.Web/Models/ContentEditing/Language.cs b/src/Umbraco.Web/Models/ContentEditing/Language.cs index f78d2bd28f..75dd07bf09 100644 --- a/src/Umbraco.Web/Models/ContentEditing/Language.cs +++ b/src/Umbraco.Web/Models/ContentEditing/Language.cs @@ -1,6 +1,5 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Runtime.CompilerServices; using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing @@ -20,9 +19,12 @@ namespace Umbraco.Web.Models.ContentEditing public string Name { get; set; } [DataMember(Name = "isDefault")] - public bool IsDefaultVariantLanguage { get; set; } + public bool IsDefault { get; set; } [DataMember(Name = "isMandatory")] - public bool Mandatory { get; set; } + public bool IsMandatory { get; set; } + + [DataMember(Name = "fallbackLanguageId")] + public int? FallbackLanguageId { get; set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs index d979ffbf4e..0118645b60 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Runtime.Serialization; using Umbraco.Core.Models; +using Umbraco.Core.Models.ContentEditing; namespace Umbraco.Web.Models.ContentEditing { diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberListDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MemberListDisplay.cs index ae9469989a..592bd14df5 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberListDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberListDisplay.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Runtime.Serialization; using Umbraco.Core.Models; +using Umbraco.Core.Models.ContentEditing; namespace Umbraco.Web.Models.ContentEditing { diff --git a/src/Umbraco.Web/Models/ContentEditing/Notification.cs b/src/Umbraco.Web/Models/ContentEditing/Notification.cs index 495b80eb83..1ed20d27d4 100644 --- a/src/Umbraco.Web/Models/ContentEditing/Notification.cs +++ b/src/Umbraco.Web/Models/ContentEditing/Notification.cs @@ -20,9 +20,12 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "header")] public string Header { get; set; } + [DataMember(Name = "message")] public string Message { get; set; } + [DataMember(Name = "type")] public SpeechBubbleIcon NotificationType { get; set; } + } } diff --git a/src/Umbraco.Web/Models/ContentEditing/UnpublishContent.cs b/src/Umbraco.Web/Models/ContentEditing/UnpublishContent.cs new file mode 100644 index 0000000000..22cb43f467 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UnpublishContent.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Used to unpublish content and variants + /// + [DataContract(Name = "unpublish", Namespace = "")] + public class UnpublishContent + { + [DataMember(Name = "id")] + public int Id { get; set; } + + [DataMember(Name = "cultures")] + public string[] Cultures { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentAppResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentAppResolver.cs index 3821a56129..a199c7e60e 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentAppResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentAppResolver.cs @@ -1,56 +1,26 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using AutoMapper; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; +using Umbraco.Core.Models.ContentEditing; +using Umbraco.Web.ContentApps; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping { - + // injected into ContentMapperProfile, + // maps ContentApps when mapping IContent to ContentItemDisplay internal class ContentAppResolver : IValueResolver> { - private readonly ContentApp _contentApp = new ContentApp - { - Alias = "content", - Name = "Content", - Icon = "icon-document", - View = "views/content/apps/content/content.html" - }; + private readonly ContentAppDefinitionCollection _contentAppDefinitions; - private readonly ContentApp _infoApp = new ContentApp + public ContentAppResolver(ContentAppDefinitionCollection contentAppDefinitions) { - Alias = "info", - Name = "Info", - Icon = "icon-info", - View = "views/content/apps/info/info.html" - }; - - private readonly IDataTypeService _dataTypeService; - private readonly PropertyEditorCollection _propertyEditorCollection; - - public ContentAppResolver(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditorCollection) - { - _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); - _propertyEditorCollection = propertyEditorCollection ?? throw new ArgumentNullException(nameof(propertyEditorCollection)); + _contentAppDefinitions = contentAppDefinitions; } public IEnumerable Resolve(IContent source, ContentItemDisplay destination, IEnumerable destMember, ResolutionContext context) { - var apps = new List(); - - if (source.ContentType.IsContainer) - { - //If it's a container then add the list view app and view model - apps.AppendListViewApp(_dataTypeService, _propertyEditorCollection, source.ContentType.Alias, "content"); - } - - apps.Add(_contentApp); - apps.Add(_infoApp); - - return apps; + return _contentAppDefinitions.GetContentAppsFor(source); } -} - + } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentAppResolverExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentAppResolverExtensions.cs deleted file mode 100644 index 8fc77beb6b..0000000000 --- a/src/Umbraco.Web/Models/Mapping/ContentAppResolverExtensions.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using AutoMapper; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - internal static class ContentAppResolverExtensions - { - /// - /// Helper method to append a list view app to the content app collection - /// - /// - public static void AppendListViewApp( - this ICollection list, - IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors, - string contentTypeAlias, string entityType) - { - var listViewApp = new ContentApp - { - Alias = "childItems", - Name = "Child items", - Icon = "icon-list", - View = "views/content/apps/listview/listview.html" - }; - - var customDtdName = Core.Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; - var dtdId = Core.Constants.DataTypes.DefaultContentListView; - //first try to get the custom one if there is one - var dt = dataTypeService.GetDataType(customDtdName) - ?? dataTypeService.GetDataType(dtdId); - - if (dt == null) - { - throw new InvalidOperationException("No list view data type was found for this document type, ensure that the default list view data types exists and/or that your custom list view data type exists"); - } - - var editor = propertyEditors[dt.EditorAlias]; - if (editor == null) - { - throw new NullReferenceException("The property editor with alias " + dt.EditorAlias + " does not exist"); - } - - var listViewConfig = editor.GetConfigurationEditor().ToConfigurationEditor(dt.Configuration); - //add the entity type to the config - listViewConfig["entityType"] = entityType; - - //Override Tab Label if tabName is provided - if (listViewConfig.ContainsKey("tabName")) - { - var configTabName = listViewConfig["tabName"]; - if (configTabName != null && string.IsNullOrWhiteSpace(configTabName.ToString()) == false) - listViewApp.Name = configTabName.ToString(); - } - - //This is the view model used for the list view app - listViewApp.ViewModel = new List - { - new ContentPropertyDisplay - { - Alias = $"{Core.Constants.PropertyEditors.InternalGenericPropertiesPrefix}containerView", - Label = "", - Value = null, - View = editor.GetValueEditor().View, - HideLabel = true, - Config = listViewConfig - } - }; - - list.Add(listViewApp); - } - } - -} diff --git a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs index 5742b7b17c..a627eab184 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs @@ -39,7 +39,7 @@ namespace Umbraco.Web.Models.Mapping { //We need to set the culture in the mapping context since this is needed to ensure that the correct property values //are resolved during the mapping - context.Items[ResolutionContextExtensions.CultureKey] = x.IsoCode; + context.Options.SetCulture(x.IsoCode); return context.Mapper.Map(source, null, context); }).ToList(); diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs index 3c8a33c625..8b23763763 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs @@ -1,9 +1,8 @@ -using System.Linq; +using System; +using System.Linq; using AutoMapper; using Umbraco.Core; -using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Trees; @@ -21,12 +20,9 @@ namespace Umbraco.Web.Models.Mapping TabsAndPropertiesResolver tabsAndPropertiesResolver, ContentAppResolver contentAppResolver, IUserService userService, - ILocalizedTextService textService, IContentService contentService, IContentTypeService contentTypeService, - IDataTypeService dataTypeService, - ILocalizationService localizationService, - ILogger logger) + ILocalizationService localizationService) { // create, capture, cache var contentOwnerResolver = new OwnerResolver(userService); @@ -36,7 +32,6 @@ namespace Umbraco.Web.Models.Mapping var contentTypeBasicResolver = new ContentTypeBasicResolver(); var defaultTemplateResolver = new DefaultTemplateResolver(); var variantResolver = new ContentVariantResolver(localizationService); - var contentSavedStateResolver = new ContentSavedStateResolver(); //FROM IContent TO ContentItemDisplay CreateMap() @@ -70,7 +65,8 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.PublishDate, opt => opt.MapFrom(src => src.PublishDate)) .ForMember(dest => dest.Segment, opt => opt.Ignore()) .ForMember(dest => dest.Language, opt => opt.Ignore()) - .ForMember(dest => dest.State, opt => opt.ResolveUsing(contentSavedStateResolver)) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.State, opt => opt.ResolveUsing>()) .ForMember(dest => dest.Tabs, opt => opt.ResolveUsing(tabsAndPropertiesResolver)); //FROM IContent TO ContentItemBasic @@ -83,11 +79,61 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.Trashed, opt => opt.MapFrom(src => src.Trashed)) .ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias)) .ForMember(dest => dest.Alias, opt => opt.Ignore()) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .ForMember(dest => dest.UpdateDate, opt => opt.ResolveUsing()) + .ForMember(dest => dest.Name, opt => opt.ResolveUsing()) + .ForMember(dest => dest.State, opt => opt.ResolveUsing>()); //FROM IContent TO ContentPropertyCollectionDto //NOTE: the property mapping for cultures relies on a culture being set in the mapping context CreateMap(); } - } + + /// + /// Resolves the update date for a content item/content variant + /// + private class UpdateDateResolver : IValueResolver, DateTime> + { + public DateTime Resolve(IContent source, ContentItemBasic destination, DateTime destMember, ResolutionContext context) + { + // invariant = global date + if (!source.ContentType.VariesByCulture()) return source.UpdateDate; + + // variant = depends on culture + var culture = context.Options.GetCulture(); + + // if there's no culture here, the issue is somewhere else (UI, whatever) - throw! + if (culture == null) + throw new InvalidOperationException("Missing culture in mapping options."); + + // if we don't have a date for a culture, it means the culture is not available, and + // hey we should probably not be mapping it, but it's too late, return a fallback date + var date = source.GetUpdateDate(culture); + return date ?? source.UpdateDate; + } + } + + /// + /// Resolves the name for a content item/content variant + /// + private class NameResolver : IValueResolver, string> + { + public string Resolve(IContent source, ContentItemBasic destination, string destMember, ResolutionContext context) + { + // invariant = only 1 name + if (!source.ContentType.VariesByCulture()) return source.Name; + + // variant = depends on culture + var culture = context.Options.GetCulture(); + + // if there's no culture here, the issue is somewhere else (UI, whatever) - throw! + if (culture == null) + throw new InvalidOperationException("Missing culture in mapping options."); + + // if we don't have a name for a culture, it means the culture is not available, and + // hey we should probably not be mapping it, but it's too late, return a fallback name + return source.CultureNames.TryGetValue(culture, out var name) && !name.IsNullOrWhiteSpace() ? name : $"(({source.Name}))"; + } + } + } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs index d9506897c8..006954fcb2 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -57,17 +57,12 @@ namespace Umbraco.Web.Models.Mapping // if there's a set of property aliases specified, we will check if the current property's value should be mapped. // if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value. - if (context.Options.Items.ContainsKey("IncludeProperties")) - { - if (context.Options.Items["IncludeProperties"] is IEnumerable includeProperties - && includeProperties.Contains(property.Alias) == false) - { - return result; - } - } + var includedProperties = context.Options.GetIncludedProperties(); + if (includedProperties != null && !includedProperties.Contains(property.Alias)) + return result; //Get the culture from the context which will be set during the mapping operation for each property - var culture = context.GetCulture(); + var culture = context.Options.GetCulture(); //a culture needs to be in the context for a property type that can vary if (culture == null && property.PropertyType.VariesByCulture()) diff --git a/src/Umbraco.Web/Models/Mapping/ContentSavedStateResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentSavedStateResolver.cs index 4fb4d3370a..2b0395b2c6 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentSavedStateResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentSavedStateResolver.cs @@ -6,17 +6,39 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping { - internal class ContentSavedStateResolver : IValueResolver + + /// + /// Returns the for an item + /// + /// + internal class ContentBasicSavedStateResolver : IValueResolver, ContentSavedState?> + where T : ContentPropertyBasic { - public ContentSavedState Resolve(IContent source, ContentVariantDisplay destination, ContentSavedState destMember, ResolutionContext context) + private readonly ContentSavedStateResolver _inner = new ContentSavedStateResolver(); + + public ContentSavedState? Resolve(IContent source, IContentProperties destination, ContentSavedState? destMember, ResolutionContext context) + { + return _inner.Resolve(source, destination, default, context); + } + } + + /// + /// Returns the for an item + /// + /// + internal class ContentSavedStateResolver : IValueResolver, ContentSavedState> + where T : ContentPropertyBasic + { + public ContentSavedState Resolve(IContent source, IContentProperties destination, ContentSavedState destMember, ResolutionContext context) { PublishedState publishedState; bool isEdited; + bool isCreated; if (source.ContentType.VariesByCulture()) { //Get the culture from the context which will be set during the mapping operation for each variant - var culture = context.GetCulture(); + var culture = context.Options.GetCulture(); //a culture needs to be in the context for a variant content item if (culture == null) @@ -29,6 +51,7 @@ namespace Umbraco.Web.Models.Mapping : PublishedState.Unpublished; isEdited = source.IsCultureEdited(culture); + isCreated = source.Id > 0 && source.IsCultureAvailable(culture); } else { @@ -37,10 +60,14 @@ namespace Umbraco.Web.Models.Mapping : PublishedState.Published; isEdited = source.Edited; + isCreated = source.Id > 0; } + if (!isCreated) + return ContentSavedState.NotCreated; + if (publishedState == PublishedState.Unpublished) - return isEdited && source.Id > 0 ? ContentSavedState.Draft : ContentSavedState.NotCreated; + return ContentSavedState.Draft; if (publishedState == PublishedState.Published) return isEdited ? ContentSavedState.PublishedPendingChanges : ContentSavedState.Published; diff --git a/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs b/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs index 44ac768fb4..40ebc801eb 100644 --- a/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs @@ -1,4 +1,5 @@ using AutoMapper; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs index e262753b83..cca64eb164 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs @@ -11,10 +11,17 @@ namespace Umbraco.Web.Models.Mapping { internal class DataTypeConfigurationFieldDisplayResolver { + private readonly ILogger _logger; + + public DataTypeConfigurationFieldDisplayResolver(ILogger logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + /// /// Maps pre-values in the dictionary to the values for the fields /// - internal static void MapConfigurationFields(DataTypeConfigurationFieldDisplay[] fields, IDictionary configuration) + internal static void MapConfigurationFields(ILogger logger, DataTypeConfigurationFieldDisplay[] fields, IDictionary configuration) { if (fields == null) throw new ArgumentNullException(nameof(fields)); if (configuration == null) throw new ArgumentNullException(nameof(configuration)); @@ -24,8 +31,12 @@ namespace Umbraco.Web.Models.Mapping { if (configuration.TryGetValue(field.Key, out var value)) field.Value = value; - else // weird - just leave the field without a value - but warn - Current.Logger.Warn("Could not find a value for configuration field '{ConfigField}'", field.Key); + else + { + // weird - just leave the field without a value - but warn + logger.Warn("Could not find a value for configuration field '{ConfigField}'", field.Key); + } + } } @@ -45,7 +56,7 @@ namespace Umbraco.Web.Models.Mapping var fields = configurationEditor.Fields.Select(Mapper.Map).ToArray(); var configurationDictionary = configurationEditor.ToConfigurationEditor(dataType.Configuration); - MapConfigurationFields(fields, configurationDictionary); + MapConfigurationFields(_logger, fields, configurationDictionary); return fields; } diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs index 92a0be1346..b74d563e88 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using AutoMapper; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -16,11 +17,11 @@ namespace Umbraco.Web.Models.Mapping /// internal class DataTypeMapperProfile : Profile { - public DataTypeMapperProfile(PropertyEditorCollection propertyEditors) + public DataTypeMapperProfile(PropertyEditorCollection propertyEditors, ILogger logger) { // create, capture, cache var availablePropertyEditorsResolver = new AvailablePropertyEditorsResolver(UmbracoConfig.For.UmbracoSettings().Content); - var configurationDisplayResolver = new DataTypeConfigurationFieldDisplayResolver(); + var configurationDisplayResolver = new DataTypeConfigurationFieldDisplayResolver(logger); var databaseTypeResolver = new DatabaseTypeResolver(); CreateMap(); @@ -119,7 +120,7 @@ namespace Umbraco.Web.Models.Mapping var defaultConfiguration = configurationEditor.DefaultConfiguration; if (defaultConfiguration != null) - DataTypeConfigurationFieldDisplayResolver.MapConfigurationFields(fields, defaultConfiguration); + DataTypeConfigurationFieldDisplayResolver.MapConfigurationFields(logger, fields, defaultConfiguration); return fields; }); diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs index 917f61a429..bfabfd799a 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs @@ -25,6 +25,7 @@ namespace Umbraco.Web.Models.Mapping var contentTypeUdiResolver = new ContentTypeUdiResolver(); CreateMap() + .ForMember(dest => dest.Name, opt => opt.ResolveUsing()) .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(ObjectTypes.GetUdiType(src.NodeObjectType), src.Key))) .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => GetContentTypeIcon(src))) .ForMember(dest => dest.Trashed, opt => opt.MapFrom(src => src.Trashed)) @@ -179,5 +180,33 @@ namespace Umbraco.Web.Models.Mapping CreateMap, IEnumerable>() .ConvertUsing(results => results.Select(Mapper.Map).ToList()); } + + /// + /// Resolves the name for a content item/content variant + /// + private class NameResolver : IValueResolver + { + public string Resolve(EntitySlim source, EntityBasic destination, string destMember, ResolutionContext context) + { + if (!(source is DocumentEntitySlim doc)) + return source.Name; + + // invariant = only 1 name + if (!doc.Variations.VariesByCulture()) return source.Name; + + // variant = depends on culture + var culture = context.Options.GetCulture(); + + // if there's no culture here, the issue is somewhere else (UI, whatever) - throw! + if (culture == null) + //throw new InvalidOperationException("Missing culture in mapping options."); + // fixme we should throw, but this is used in various places that won't set a culture yet + return source.Name; + + // if we don't have a name for a culture, it means the culture is not available, and + // hey we should probably not be mapping it, but it's too late, return a fallback name + return doc.CultureNames.TryGetValue(culture, out var name) && !name.IsNullOrWhiteSpace() ? name : $"(({source.Name}))"; + } + } } } diff --git a/src/Umbraco.Web/Models/Mapping/MappingOperationOptionsExtensions.cs b/src/Umbraco.Web/Models/Mapping/MappingOperationOptionsExtensions.cs new file mode 100644 index 0000000000..e704272ddb --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MappingOperationOptionsExtensions.cs @@ -0,0 +1,45 @@ +using AutoMapper; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Provides extension methods for AutoMapper's . + /// + internal static class MappingOperationOptionsExtensions + { + private const string CultureKey = "MappingOperationOptions.Culture"; + private const string IncludedPropertiesKey = "MappingOperationOptions.IncludeProperties"; + + /// + /// Gets the context culture. + /// + public static string GetCulture(this IMappingOperationOptions options) + { + return options.Items.TryGetValue(CultureKey, out var obj) && obj is string s ? s : null; + } + + /// + /// Sets a context culture. + /// + public static void SetCulture(this IMappingOperationOptions options, string culture) + { + options.Items[CultureKey] = culture; + } + + /// + /// Get included properties. + /// + public static string[] GetIncludedProperties(this IMappingOperationOptions options) + { + return options.Items.TryGetValue(IncludedPropertiesKey, out var obj) && obj is string[] s ? s : null; + } + + /// + /// Sets included properties. + /// + public static void SetIncludedProperties(this IMappingOperationOptions options, string[] properties) + { + options.Items[IncludedPropertiesKey] = properties; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/MediaAppResolver.cs b/src/Umbraco.Web/Models/Mapping/MediaAppResolver.cs index 6880aff9a8..caaaacc5f2 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaAppResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaAppResolver.cs @@ -1,57 +1,26 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using AutoMapper; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; +using Umbraco.Core.Models.ContentEditing; +using Umbraco.Web.ContentApps; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping { + // injected into ContentMapperProfile, + // maps ContentApps when mapping IMedia to MediaItemDisplay internal class MediaAppResolver : IValueResolver> { - private static readonly ContentApp _contentApp = new ContentApp - { - Alias = "content", - Name = "Content", - Icon = "icon-document", - View = "views/media/apps/content/content.html" - }; + private readonly ContentAppDefinitionCollection _contentAppDefinitions; - private static readonly ContentApp _infoApp = new ContentApp + public MediaAppResolver(ContentAppDefinitionCollection contentAppDefinitions) { - Alias = "info", - Name = "Info", - Icon = "icon-info", - View = "views/media/apps/info/info.html" - }; - - private readonly IDataTypeService _dataTypeService; - private readonly PropertyEditorCollection _propertyEditorCollection; - - public MediaAppResolver(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditorCollection) - { - _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); - _propertyEditorCollection = propertyEditorCollection ?? throw new ArgumentNullException(nameof(propertyEditorCollection)); + _contentAppDefinitions = contentAppDefinitions; } public IEnumerable Resolve(IMedia source, MediaItemDisplay destination, IEnumerable destMember, ResolutionContext context) { - var apps = new List(); - - if (source.ContentType.IsContainer || source.ContentType.Alias == Core.Constants.Conventions.MediaTypes.Folder) - { - apps.AppendListViewApp(_dataTypeService, _propertyEditorCollection, source.ContentType.Alias, "media"); - } - else - { - apps.Add(_contentApp); - } - - apps.Add(_infoApp); - - return apps; + return _contentAppDefinitions.GetContentAppsFor(source); } } - } diff --git a/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs index caa9f29e70..c07d66b6e2 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs @@ -44,7 +44,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.TreeNodeUrl, opt => opt.ResolveUsing(contentTreeNodeUrlResolver)) .ForMember(dest => dest.Notifications, opt => opt.Ignore()) .ForMember(dest => dest.Errors, opt => opt.Ignore()) - .ForMember(dest => dest.Published, opt => opt.Ignore()) + .ForMember(dest => dest.State, opt => opt.UseValue(null)) .ForMember(dest => dest.Edited, opt => opt.Ignore()) .ForMember(dest => dest.Updater, opt => opt.Ignore()) .ForMember(dest => dest.Alias, opt => opt.Ignore()) @@ -62,7 +62,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon)) .ForMember(dest => dest.Trashed, opt => opt.MapFrom(src => src.Trashed)) .ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias)) - .ForMember(dest => dest.Published, opt => opt.Ignore()) + .ForMember(dest => dest.State, opt => opt.UseValue(null)) .ForMember(dest => dest.Edited, opt => opt.Ignore()) .ForMember(dest => dest.Updater, opt => opt.Ignore()) .ForMember(dest => dest.Alias, opt => opt.Ignore()) diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs index 22d499f3d6..f64d8dc529 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs @@ -73,7 +73,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.MembershipScenario, opt => opt.ResolveUsing(src => membershipScenarioMappingResolver.Resolve(src))) .ForMember(dest => dest.Notifications, opt => opt.Ignore()) .ForMember(dest => dest.Errors, opt => opt.Ignore()) - .ForMember(dest => dest.Published, opt => opt.Ignore()) + .ForMember(dest => dest.State, opt => opt.UseValue(null)) .ForMember(dest => dest.Edited, opt => opt.Ignore()) .ForMember(dest => dest.Updater, opt => opt.Ignore()) .ForMember(dest => dest.Alias, opt => opt.Ignore()) @@ -91,7 +91,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email)) .ForMember(dest => dest.Username, opt => opt.MapFrom(src => src.Username)) .ForMember(dest => dest.Trashed, opt => opt.Ignore()) - .ForMember(dest => dest.Published, opt => opt.Ignore()) + .ForMember(dest => dest.State, opt => opt.UseValue(null)) .ForMember(dest => dest.Edited, opt => opt.Ignore()) .ForMember(dest => dest.Updater, opt => opt.Ignore()) .ForMember(dest => dest.Alias, opt => opt.Ignore()) @@ -115,7 +115,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.Path, opt => opt.Ignore()) .ForMember(dest => dest.SortOrder, opt => opt.Ignore()) .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) - .ForMember(dest => dest.Published, opt => opt.Ignore()) + .ForMember(dest => dest.State, opt => opt.UseValue(ContentSavedState.Draft)) .ForMember(dest => dest.Edited, opt => opt.Ignore()) .ForMember(dest => dest.Updater, opt => opt.Ignore()) .ForMember(dest => dest.Trashed, opt => opt.Ignore()) diff --git a/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs b/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs index 68e1a1ff91..76937886a9 100644 --- a/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs @@ -1,4 +1,5 @@ using AutoMapper; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; @@ -22,7 +23,8 @@ namespace Umbraco.Web.Models.Mapping public UserProfile Resolve(TPersisted source) { - return Mapper.Map(source.GetCreatorProfile(_userService)); + var profile = source.GetCreatorProfile(_userService); + return profile == null ? null : Mapper.Map(profile); } } } diff --git a/src/Umbraco.Web/Models/Mapping/ResolutionContextExtensions.cs b/src/Umbraco.Web/Models/Mapping/ResolutionContextExtensions.cs deleted file mode 100644 index 27f00ce91c..0000000000 --- a/src/Umbraco.Web/Models/Mapping/ResolutionContextExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using AutoMapper; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Extension methods for AutoMapper's - /// - internal static class ResolutionContextExtensions - { - public const string CultureKey = "ContextMapper.Culture"; - - /// - /// Returns the language Id in the mapping context if one is found - /// - /// - /// - public static string GetCulture(this ResolutionContext resolutionContext) - { - if (!resolutionContext.Options.Items.TryGetValue(CultureKey, out var obj)) return null; - - if (obj is string s) - return s; - - return null; - } - } -} - diff --git a/src/Umbraco.Web/Models/Mapping/SectionMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/SectionMapperProfile.cs index 11b4deff48..ba7ff3c38d 100644 --- a/src/Umbraco.Web/Models/Mapping/SectionMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/SectionMapperProfile.cs @@ -11,8 +11,9 @@ namespace Umbraco.Web.Models.Mapping { CreateMap() .ForMember(dest => dest.RoutePath, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.Ignore()) .ForMember(dest => dest.Name, opt => opt.MapFrom(src => textService.Localize("sections/" + src.Alias, (IDictionary)null))) - .ReverseMap(); //backwards too! + .ReverseMap(); //backwards too! } } } diff --git a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs index 47e4b3d872..d0218b6639 100644 --- a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs @@ -1,4 +1,8 @@ -using Umbraco.Core.Models.PublishedContent; +using System; +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; namespace Umbraco.Web.Models.PublishedContent { @@ -7,86 +11,283 @@ namespace Umbraco.Web.Models.PublishedContent /// public class PublishedValueFallback : IPublishedValueFallback { - // this is our default implementation - // kinda reproducing what was available in v7 + private readonly ILocalizationService _localizationService; + private readonly IVariationContextAccessor _variationContextAccessor; - /// - public object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue) + /// + /// Initializes a new instance of the class. + /// + public PublishedValueFallback(ServiceContext serviceContext, IVariationContextAccessor variationContextAccessor) { - // no fallback here - return defaultValue; + _localizationService = serviceContext.LocalizationService; + _variationContextAccessor = variationContextAccessor; } /// - public T GetValue(IPublishedProperty property, string culture, string segment, T defaultValue) + public bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value) { - // no fallback here - return defaultValue; + return TryGetValue(property, culture, segment, fallback, defaultValue, out value); } /// - public object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue) + public bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value) { - // no fallback here - return defaultValue; + _variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, ref culture, ref segment); + + foreach (var f in fallback) + { + switch (f) + { + case Fallback.None: + continue; + case Fallback.DefaultValue: + value = defaultValue; + return true; + case Fallback.Language: + if (TryGetValueWithLanguageFallback(property, culture, segment, defaultValue, out value)) + return true; + break; + default: + throw NotSupportedFallbackMethod(f, "property"); + } + } + + value = defaultValue; + return false; } /// - public T GetValue(IPublishedElement content, string alias, string culture, string segment, T defaultValue) + public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value) { - // no fallback here - return defaultValue; + return TryGetValue(content, alias, culture, segment, fallback, defaultValue, out value); } /// - public object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse) + public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value) { - // no fallback here - if (!recurse) return defaultValue; + var propertyType = content.ContentType.GetPropertyType(alias); + if (propertyType == null) + { + value = default; + return false; + } + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + foreach (var f in fallback) + { + switch (f) + { + case Fallback.None: + continue; + case Fallback.DefaultValue: + value = defaultValue; + return true; + case Fallback.Language: + if (TryGetValueWithLanguageFallback(content, alias, culture, segment, defaultValue, out value)) + return true; + break; + default: + throw NotSupportedFallbackMethod(f, "element"); + } + } + + value = defaultValue; + return false; + } + + /// + public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value) + { // is that ok? - return GetValue(content, alias, culture, segment, defaultValue, recurse); + return TryGetValue(content, alias, culture, segment, fallback, defaultValue, out value); } /// - public T GetValue(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse) + public virtual bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value) { - // no fallback here - if (!recurse) return defaultValue; + var propertyType = content.ContentType.GetPropertyType(alias); + if (propertyType == null) + { + value = default; + return false; + } + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); - // otherwise, implement recursion as it was implemented in PublishedContentBase + // note: we don't support "recurse & language" which would walk up the tree, + // looking at languages at each level - should someone need it... they'll have + // to implement it. - // fixme caching? - // - // all caches were using PublishedContentBase.GetProperty(alias, recurse) to get the property, - // then, - // NuCache.PublishedContent was storing the property in GetAppropriateCache() with key "NuCache.Property.Recurse[" + DraftOrPub(previewing) + contentUid + ":" + typeAlias + "]"; - // XmlPublishedContent was storing the property in _cacheProvider with key $"XmlPublishedCache.PublishedContentCache:RecursiveProperty-{Id}-{alias.ToLowerInvariant()}"; - // DictionaryPublishedContent was storing the property in _cacheProvider with key $"XmlPublishedCache.PublishedMediaCache:RecursiveProperty-{Id}-{alias.ToLowerInvariant()}"; - // - // at the moment, caching has been entirely removed, until we better understand caching + fallback + foreach (var f in fallback) + { + switch (f) + { + case Fallback.None: + continue; + case Fallback.DefaultValue: + value = defaultValue; + return true; + case Fallback.Language: + if (TryGetValueWithLanguageFallback(content, alias, culture, segment, defaultValue, out value)) + return true; + break; + case Fallback.Ancestors: + if (TryGetValueWithRecursiveFallback(content, alias, culture, segment, defaultValue, out value)) + return true; + break; + default: + throw NotSupportedFallbackMethod(f, "content"); + } + } + value = defaultValue; + return false; + } + + private NotSupportedException NotSupportedFallbackMethod(int fallback, string level) + { + return new NotSupportedException($"Fallback {GetType().Name} does not support fallback code '{fallback}' at {level} level."); + } + + // tries to get a value, recursing the tree + private static bool TryGetValueWithRecursiveFallback(IPublishedContent content, string alias, string culture, string segment, T defaultValue, out T value) + { IPublishedProperty property = null; // if we are here, content's property has no value IPublishedProperty noValueProperty = null; do { content = content.Parent; property = content?.GetProperty(alias); - if (property != null) noValueProperty = property; - } while (content != null && (property == null || property.HasValue(culture, segment) == false)); + if (property != null) + { + noValueProperty = property; + } + } + while (content != null && (property == null || property.HasValue(culture, segment) == false)); // if we found a content with the property having a value, return that property value if (property != null && property.HasValue(culture, segment)) - return property.Value(culture, segment); + { + value = property.Value(culture, segment); + return true; + } // if we found a property, even though with no value, return that property value // because the converter may want to handle the missing value. ie if defaultValue is default, // either specified or by default, the converter may want to substitute something else. if (noValueProperty != null) - return noValueProperty.Value(culture, segment, defaultValue: defaultValue); + { + value = noValueProperty.Value(culture, segment, defaultValue: defaultValue); + return true; + } - // else return default - return defaultValue; + value = defaultValue; + return false; + } + + // tries to get a value, falling back onto other languages + private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string culture, string segment, T defaultValue, out T value) + { + value = defaultValue; + + if (culture.IsNullOrWhiteSpace()) return false; + + var visited = new HashSet(); + + var language = _localizationService.GetLanguageByIsoCode(culture); + if (language == null) return false; + + while (true) + { + if (language.FallbackLanguageId == null) return false; + + var language2Id = language.FallbackLanguageId.Value; + if (visited.Contains(language2Id)) return false; + visited.Add(language2Id); + + var language2 = _localizationService.GetLanguageById(language2Id); + if (language2 == null) return false; + var culture2 = language2.IsoCode; + + if (property.HasValue(culture2, segment)) + { + value = property.Value(culture2, segment); + return true; + } + + language = language2; + } + } + + // tries to get a value, falling back onto other languages + private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string culture, string segment, T defaultValue, out T value) + { + value = defaultValue; + + if (culture.IsNullOrWhiteSpace()) return false; + + var visited = new HashSet(); + + var language = _localizationService.GetLanguageByIsoCode(culture); + if (language == null) return false; + + while (true) + { + if (language.FallbackLanguageId == null) return false; + + var language2Id = language.FallbackLanguageId.Value; + if (visited.Contains(language2Id)) return false; + visited.Add(language2Id); + + var language2 = _localizationService.GetLanguageById(language2Id); + if (language2 == null) return false; + var culture2 = language2.IsoCode; + + if (content.HasValue(alias, culture2, segment)) + { + value = content.Value(alias, culture2, segment); + return true; + } + + language = language2; + } + } + + // tries to get a value, falling back onto other languages + private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string culture, string segment, T defaultValue, out T value) + { + value = defaultValue; + + if (culture.IsNullOrWhiteSpace()) return false; + + var visited = new HashSet(); + + // fixme + // _localizationService.GetXxx() is expensive, it deep clones objects + // we want _localizationService.GetReadOnlyXxx() returning IReadOnlyLanguage which cannot be saved back = no need to clone + + var language = _localizationService.GetLanguageByIsoCode(culture); + if (language == null) return false; + + while (true) + { + if (language.FallbackLanguageId == null) return false; + + var language2Id = language.FallbackLanguageId.Value; + if (visited.Contains(language2Id)) return false; + visited.Add(language2Id); + + var language2 = _localizationService.GetLanguageById(language2Id); + if (language2 == null) return false; + var culture2 = language2.IsoCode; + + if (content.HasValue(alias, culture2, segment)) + { + value = content.Value(alias, culture2, segment); + return true; + } + + language = language2; + } } } } diff --git a/src/Umbraco.Web/Models/Trees/ApplicationAttribute.cs b/src/Umbraco.Web/Models/Trees/ApplicationAttribute.cs index 221346cd23..65fc4ed6f3 100644 --- a/src/Umbraco.Web/Models/Trees/ApplicationAttribute.cs +++ b/src/Umbraco.Web/Models/Trees/ApplicationAttribute.cs @@ -13,22 +13,18 @@ namespace Umbraco.Web.Models.Trees /// /// The alias. /// The name. - /// The icon. /// The sort order. public ApplicationAttribute(string alias, string name, - string icon, int sortOrder = 0) { Alias = alias; Name = name; - Icon = icon; SortOrder = sortOrder; } public string Alias { get; private set; } public string Name { get; private set; } - public string Icon { get; private set; } public int SortOrder { get; private set; } } } diff --git a/src/Umbraco.Web/Models/Trees/ApplicationDefinitions.cs b/src/Umbraco.Web/Models/Trees/ApplicationDefinitions.cs index 002b96bc64..aa8e0cfda6 100644 --- a/src/Umbraco.Web/Models/Trees/ApplicationDefinitions.cs +++ b/src/Umbraco.Web/Models/Trees/ApplicationDefinitions.cs @@ -7,31 +7,31 @@ namespace Umbraco.Web.Models.Trees // the application.config. On app startup, Umbraco will look for any // unregistered classes with an ApplicationAttribute and add them to the cache - [Application(Constants.Applications.Content, "Content", ".traycontent")] + [Application(Constants.Applications.Content, "Content")] public class ContentApplicationDefinition : IApplication { } - [Application(Constants.Applications.Media, "Media", ".traymedia", sortOrder: 1)] + [Application(Constants.Applications.Media, "Media", sortOrder: 1)] public class MediaApplicationDefinition : IApplication { } - [Application(Constants.Applications.Settings, "Settings", ".traysettings", sortOrder: 2)] + [Application(Constants.Applications.Settings, "Settings", sortOrder: 2)] public class SettingsApplicationDefinition : IApplication { } - [Application(Constants.Applications.Developer, "Developer", ".traydeveloper", sortOrder: 3)] - public class DeveloperApplicationDefinition : IApplication + [Application(Constants.Applications.Packages, "Packages", sortOrder: 3)] + public class PackagesApplicationDefinition : IApplication { } - [Application(Constants.Applications.Users, "Users", ".trayusers", sortOrder: 4)] + [Application(Constants.Applications.Users, "Users", sortOrder: 4)] public class UsersApplicationDefinition : IApplication { } - [Application(Constants.Applications.Members, "Members", ".traymember", sortOrder: 5)] + [Application(Constants.Applications.Members, "Members", sortOrder: 5)] public class MembersApplicationDefinition : IApplication { } - [Application(Constants.Applications.Translation, "Translation", ".traytranslation", sortOrder: 6)] + [Application(Constants.Applications.Translation, "Translation", sortOrder: 6)] public class TranslationApplicationDefinition : IApplication { } } diff --git a/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs b/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs index 73329276af..9f8ca9b84d 100644 --- a/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs +++ b/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs @@ -54,7 +54,7 @@ /// Sets the node style to show that it is has unpublished versions (but is currently published) /// /// - public static void SetHasUnpublishedVersionStyle(this TreeNode treeNode) + public static void SetHasPendingVersionStyle(this TreeNode treeNode) { if (treeNode.CssClasses.Contains("has-unpublished-version") == false) { diff --git a/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs b/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs index bcf7b34b4f..380ec4cd4e 100644 --- a/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs +++ b/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs @@ -9,9 +9,9 @@ namespace Umbraco.Web.Mvc /// public class DisableBrowserCacheAttribute : ActionFilterAttribute { - public override void OnActionExecuted(ActionExecutedContext filterContext) + public override void OnResultExecuting(ResultExecutingContext filterContext) { - base.OnActionExecuted(filterContext); + base.OnResultExecuting(filterContext); // could happens if exception (but afaik this wouldn't happen in MVC) if (filterContext.HttpContext == null || filterContext.HttpContext.Response == null || @@ -20,6 +20,18 @@ namespace Umbraco.Web.Mvc return; } + if (filterContext.IsChildAction) + { + return; + } + + if (filterContext.HttpContext.Response.StatusCode != 200) + { + return; + } + + filterContext.HttpContext.Response.Cache.SetLastModified(DateTime.Now); + filterContext.HttpContext.Response.Cache.SetValidUntilExpires(false); filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); filterContext.HttpContext.Response.Cache.SetMaxAge(TimeSpan.Zero); filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches); diff --git a/src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs b/src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs deleted file mode 100644 index a874d93d7a..0000000000 --- a/src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Web; -using System.Web.Mvc; - -namespace Umbraco.Web.Mvc -{ - /// - /// Will ensure that client-side cache does not occur by sending the correct response headers - /// - public class DisableClientCacheAttribute : ActionFilterAttribute - { - public override void OnResultExecuting(ResultExecutingContext filterContext) - { - if (filterContext.IsChildAction) base.OnResultExecuting(filterContext); - - filterContext.HttpContext.Response.Cache.SetExpires(DateTime.Now.AddDays(-10)); - filterContext.HttpContext.Response.Cache.SetLastModified(DateTime.Now); - filterContext.HttpContext.Response.Cache.SetValidUntilExpires(false); - filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches); - filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); - filterContext.HttpContext.Response.Cache.SetNoStore(); - - base.OnResultExecuting(filterContext); - } - } -} diff --git a/src/Umbraco.Core/Security/OwinExtensions.cs b/src/Umbraco.Web/OwinExtensions.cs similarity index 83% rename from src/Umbraco.Core/Security/OwinExtensions.cs rename to src/Umbraco.Web/OwinExtensions.cs index a4d596855c..e7e1e85b50 100644 --- a/src/Umbraco.Core/Security/OwinExtensions.cs +++ b/src/Umbraco.Web/OwinExtensions.cs @@ -3,12 +3,26 @@ using System.Web; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Microsoft.Owin.Security; +using Umbraco.Core; using Umbraco.Core.Models.Identity; +using Umbraco.Core.Security; +using Umbraco.Web.Security; -namespace Umbraco.Core.Security +namespace Umbraco.Web { public static class OwinExtensions { + /// + /// Gets the for the Umbraco back office cookie + /// + /// + /// + internal static ISecureDataFormat GetUmbracoAuthTicketDataProtector(this IOwinContext owinContext) + { + var found = owinContext.Get(); + return found?.Protector; + } + public static string GetCurrentRequestIpAddress(this IOwinContext owinContext) { if (owinContext == null) @@ -68,7 +82,6 @@ namespace Umbraco.Core.Security return marker.GetManager(owinContext) ?? throw new NullReferenceException($"Could not resolve an instance of {typeof (BackOfficeUserManager)} from the {typeof (IOwinContext)}."); } - } } diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index ac146c3251..412b8c9766 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -2,11 +2,13 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Web.Media; namespace Umbraco.Web.PropertyEditors { @@ -14,11 +16,15 @@ namespace Umbraco.Web.PropertyEditors public class FileUploadPropertyEditor : DataEditor { private readonly MediaFileSystem _mediaFileSystem; + private readonly IContentSection _contentSection; + private readonly UploadAutoFillProperties _uploadAutoFillProperties; - public FileUploadPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem) + public FileUploadPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem, IContentSection contentSection) : base(logger) { _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); + _contentSection = contentSection; + _uploadAutoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, logger, contentSection); } /// @@ -148,16 +154,16 @@ namespace Umbraco.Web.PropertyEditors foreach (var property in properties) { - var autoFillConfig = _mediaFileSystem.UploadAutoFillProperties.GetConfig(property.Alias); + var autoFillConfig = _contentSection.GetConfig(property.Alias); if (autoFillConfig == null) continue; foreach (var pvalue in property.Values) { var svalue = property.GetValue(pvalue.Culture, pvalue.Segment) as string; if (string.IsNullOrWhiteSpace(svalue)) - _mediaFileSystem.UploadAutoFillProperties.Reset(model, autoFillConfig, pvalue.Culture, pvalue.Segment); + _uploadAutoFillProperties.Reset(model, autoFillConfig, pvalue.Culture, pvalue.Segment); else - _mediaFileSystem.UploadAutoFillProperties.Populate(model, autoFillConfig, _mediaFileSystem.GetRelativePath(svalue), pvalue.Culture, pvalue.Segment); + _uploadAutoFillProperties.Populate(model, autoFillConfig, _mediaFileSystem.GetRelativePath(svalue), pvalue.Culture, pvalue.Segment); } } } diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs index be7b3ff2f8..47711507b2 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -91,6 +91,7 @@ namespace Umbraco.Web.PropertyEditors // update json and return if (editorFile == null) return null; return filepath == null ? string.Empty : _mediaFileSystem.GetUrl(filepath); + } @@ -107,6 +108,9 @@ namespace Umbraco.Web.PropertyEditors using (var filestream = File.OpenRead(file.TempFilePath)) { + //TODO: Here it would make sense to do the auto-fill properties stuff but the API doesn't allow us to do that right + // since we'd need to be able to return values for other properties from these methods + _mediaFileSystem.AddFile(filepath, filestream, true); // must overwrite! } diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index 2a74b4f05c..c8a7bfc80c 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -10,7 +10,9 @@ using Umbraco.Core.Logging; using Umbraco.Core.Media; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Services; +using Umbraco.Web.Media; namespace Umbraco.Web.PropertyEditors { @@ -21,18 +23,22 @@ namespace Umbraco.Web.PropertyEditors public class ImageCropperPropertyEditor : DataEditor { private readonly MediaFileSystem _mediaFileSystem; + private readonly IContentSection _contentSettings; + private readonly IDataTypeService _dataTypeService; private readonly UploadAutoFillProperties _autoFillProperties; /// /// Initializes a new instance of the class. /// - public ImageCropperPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem, IContentSection contentSettings) + public ImageCropperPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem, IContentSection contentSettings, IDataTypeService dataTypeService) : base(logger) { _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); - var contentSettings1 = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); + _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); + _dataTypeService = dataTypeService; - _autoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, Logger, contentSettings1); + //fixme: inject? + _autoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, logger, _contentSettings); } /// @@ -204,7 +210,7 @@ namespace Umbraco.Web.PropertyEditors foreach (var property in properties) { - var autoFillConfig = _autoFillProperties.GetConfig(property.Alias); + var autoFillConfig = _contentSettings.GetConfig(property.Alias); if (autoFillConfig == null) continue; foreach (var pvalue in property.Values) @@ -213,40 +219,40 @@ namespace Umbraco.Web.PropertyEditors if (string.IsNullOrWhiteSpace(svalue)) { _autoFillProperties.Reset(model, autoFillConfig, pvalue.Culture, pvalue.Segment); - continue; - } - - // FIXME VERY TEMP - // we should kill all auto-fill properties - // BUT that being said what would be the right way to do this? - /* - var v = JsonConvert.DeserializeObject() - - var jo = GetJObject(svalue, false); - string src; - if (jo == null) - { - // so we have a non-empty string value that cannot be parsed into a json object - // see http://issues.umbraco.org/issue/U4-4756 - // it can happen when an image is uploaded via the folder browser, in which case - // the property value will be the file source eg '/media/23454/hello.jpg' and we - // are fixing that anomaly here - does not make any sense at all but... bah... - var config = _dataTypeService - .GetPreValuesByDataTypeId(property.PropertyType.DataTypeDefinitionId).FirstOrDefault(); - var crops = string.IsNullOrWhiteSpace(config) ? "[]" : config; - src = svalue; - property.SetValue("{\"src\": \"" + svalue + "\", \"crops\": " + crops + "}"); } else { - src = jo["src"]?.Value(); - } + var jo = GetJObject(svalue, false); + string src; + if (jo == null) + { + // so we have a non-empty string value that cannot be parsed into a json object + // see http://issues.umbraco.org/issue/U4-4756 + // it can happen when an image is uploaded via the folder browser, in which case + // the property value will be the file source eg '/media/23454/hello.jpg' and we + // are fixing that anomaly here - does not make any sense at all but... bah... - if (src == null) - _autoFillProperties.Reset(model, autoFillConfig, pvalue.LanguageId, pvalue.Segment); - else - _autoFillProperties.Populate(model, autoFillConfig, _mediaFileSystem.GetRelativePath(src), pvalue.LanguageId, pvalue.Segment); - */ + var dt = _dataTypeService.GetDataType(property.PropertyType.DataTypeId); + var config = dt?.ConfigurationAs(); + src = svalue; + var json = new + { + src = svalue, + crops = config == null ? Array.Empty() : config.Crops + }; + + property.SetValue(JsonConvert.SerializeObject(json), pvalue.Culture, pvalue.Segment); + } + else + { + src = jo["src"]?.Value(); + } + + if (src == null) + _autoFillProperties.Reset(model, autoFillConfig, pvalue.Culture, pvalue.Segment); + else + _autoFillProperties.Populate(model, autoFillConfig, _mediaFileSystem.GetRelativePath(src), pvalue.Culture, pvalue.Segment); + } } } } diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs index 98e8346441..4f44352d34 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -151,6 +151,9 @@ namespace Umbraco.Web.PropertyEditors using (var filestream = File.OpenRead(file.TempFilePath)) { + //TODO: Here it would make sense to do the auto-fill properties stuff but the API doesn't allow us to do that right + // since we'd need to be able to return values for other properties from these methods + _mediaFileSystem.AddFile(filepath, filestream, true); // must overwrite! } diff --git a/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs b/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs index 7afddbf8de..507251043b 100644 --- a/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs @@ -8,6 +8,38 @@ namespace Umbraco.Web.PropertyEditors /// public class ListViewConfiguration { + public ListViewConfiguration() + { + // initialize defaults + + PageSize = 10; + DisplayAtTabNumber = 1; + OrderBy = "SortOrder"; + OrderDirection = "asc"; + + BulkActionPermissions = new BulkActionPermissionSettings + { + AllowBulkPublish = true, + AllowBulkUnpublish = true, + AllowBulkCopy = true, + AllowBulkMove = true, + AllowBulkDelete = true + }; + + Layouts = new[] + { + new Layout { Name = "List", Icon = "icon-list", IsSystem = 1, Selected = true, Path = "views/propertyeditors/listview/layouts/list/list.html" }, + new Layout { Name = "grid", Icon = "icon-thumbnails-small", IsSystem = 1, Selected = true, Path = "views/propertyeditors/listview/layouts/grid/grid.html" } + }; + + IncludeProperties = new [] + { + new Property { Alias = "sortOrder", Header = "Sort order", IsSystem = 1 }, + new Property { Alias = "updateDate", Header = "Last edited", IsSystem = 1 }, + new Property { Alias = "owner", Header = "Created by", IsSystem = 1 } + }; + } + [ConfigurationField("pageSize", "Page Size", "number", Description = "Number of items per page")] public int PageSize { get; set; } diff --git a/src/Umbraco.Web/PropertyEditors/ListViewConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/ListViewConfigurationEditor.cs index a94945df0b..8239c981d0 100644 --- a/src/Umbraco.Web/PropertyEditors/ListViewConfigurationEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ListViewConfigurationEditor.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { @@ -7,36 +6,5 @@ namespace Umbraco.Web.PropertyEditors /// Represents the configuration editor for the listview value editor. /// public class ListViewConfigurationEditor : ConfigurationEditor - { - public override IDictionary DefaultConfiguration => new Dictionary - { - {"pageSize", "10"}, - {"displayAtTabNumber", "1"}, - {"orderBy", "SortOrder"}, - {"orderDirection", "asc"}, - { - "includeProperties", new[] - { - new {alias = "sortOrder", header = "Sort order", isSystem = 1}, - new {alias = "updateDate", header = "Last edited", isSystem = 1}, - new {alias = "owner", header = "Created by", isSystem = 1} - } - }, - { - "layouts", new[] - { - new {name = "List", path = "views/propertyeditors/listview/layouts/list/list.html", icon = "icon-list", isSystem = 1, selected = true}, - new {name = "Grid", path = "views/propertyeditors/listview/layouts/grid/grid.html", icon = "icon-thumbnails-small", isSystem = 1, selected = true} - } - }, - {"bulkActionPermissions", new - { - allowBulkPublish = true, - allowBulkUnpublish = true, - allowBulkCopy = true, - allowBulkMove = false, - allowBulkDelete = true - }} - }; - } -} \ No newline at end of file + { } +} diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 4cb8fde97a..9739b7a30b 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -272,6 +272,9 @@ namespace Umbraco.Web.PropertyEditors public IEnumerable Validate(object rawValue, string valueType, object dataTypeConfiguration) { + if (rawValue == null) + yield break; + var value = JsonConvert.DeserializeObject>(rawValue.ToString()); if (value == null) yield break; diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index adb9e1a837..0b091e0d47 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Macros; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Web.Macros; namespace Umbraco.Web.PropertyEditors { diff --git a/src/Umbraco.Web/PropertyEditors/TextAreaConfiguration.cs b/src/Umbraco.Web/PropertyEditors/TextAreaConfiguration.cs index a773dbc7d2..e00252b076 100644 --- a/src/Umbraco.Web/PropertyEditors/TextAreaConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/TextAreaConfiguration.cs @@ -9,5 +9,8 @@ namespace Umbraco.Web.PropertyEditors { [ConfigurationField("maxChars", "Maximum allowed characters", "number", Description = "If empty - no character limit")] public int MaxChars { get; set; } + + [ConfigurationField("rows", "Number of rows", "number", Description = "If empty - 10 rows would be set as the default value")] + public int Rows { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/FlexibleDropdownPropertyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/FlexibleDropdownPropertyValueConverter.cs index 5fa537f561..521817a7de 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/FlexibleDropdownPropertyValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/FlexibleDropdownPropertyValueConverter.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; @@ -23,29 +22,27 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { if (inter == null) - { return null; - } - var selectedValues = (string[])inter; - if (selectedValues.Any()) + var multiple = propertyType.DataType.ConfigurationAs().Multiple; + var selectedValues = (string[]) inter; + if (selectedValues.Length > 0) { - if (propertyType.DataType.ConfigurationAs().Multiple) - { - return selectedValues; - } - - return selectedValues.First(); + return multiple + ? (object) selectedValues + : selectedValues[0]; } - return inter; + return multiple + ? inter + : string.Empty; } public override Type GetPropertyValueType(PublishedPropertyType propertyType) { return propertyType.DataType.ConfigurationAs().Multiple - ? typeof(IEnumerable) - : typeof(string); - } + ? typeof(IEnumerable) + : typeof(string); + } } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs index 2785f160bf..cd2f4e5384 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Macros; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Web.Macros; namespace Umbraco.Web.PropertyEditors.ValueConverters { diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs index 20ffc5db68..0612c85020 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var sourceString = source.ToString(); // ensures string is parsed for {localLink} and urls are resolved correctly - sourceString = TemplateUtilities.ParseInternalLinks(sourceString, preview); + sourceString = TemplateUtilities.ParseInternalLinks(sourceString, preview, UmbracoContext.Current); sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString); return sourceString; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs index f2873e9466..802f42ed9e 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs @@ -27,9 +27,16 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters } public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => IsMultipleDataType(propertyType.DataType) - ? typeof (IEnumerable) - : typeof (IPublishedContent); + { + var isMultiple = IsMultipleDataType(propertyType.DataType); + var isOnlyImages = IsOnlyImagesDataType(propertyType.DataType); + + // hard-coding "image" here but that's how it works at UI level too + + return isMultiple + ? (isOnlyImages ? typeof(IEnumerable<>).MakeGenericType(ModelType.For("image")) : typeof(IEnumerable)) + : (isOnlyImages ? ModelType.For("image") : typeof(IPublishedContent)); + } public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; @@ -40,6 +47,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return config.Multiple; } + private bool IsOnlyImagesDataType(PublishedDataType dataType) + { + var config = ConfigurationEditor.ConfigurationAs(dataType.Configuration); + return config.OnlyImages; + } + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs index 50ebe35d11..ca5489ac1f 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -83,10 +83,21 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { var guidUdi = udi as GuidUdi; if (guidUdi == null) continue; - var multiNodeTreePickerItem = - GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Document, id => _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(guidUdi.Guid)) - ?? GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Media, id => _publishedSnapshotAccessor.PublishedSnapshot.Media.GetById(guidUdi.Guid)) - ?? GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Member, id => _publishedSnapshotAccessor.PublishedSnapshot.Members.GetByProviderKey(guidUdi.Guid)); + + IPublishedContent multiNodeTreePickerItem = null; + switch (udi.EntityType) + { + case Constants.UdiEntityType.Document: + multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Document, id => _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(guidUdi.Guid)); + break; + case Constants.UdiEntityType.Media: + multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Media, id => _publishedSnapshotAccessor.PublishedSnapshot.Media.GetById(guidUdi.Guid)); + break; + case Constants.UdiEntityType.Member: + multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Member, id => _publishedSnapshotAccessor.PublishedSnapshot.Members.GetByProviderKey(guidUdi.Guid)); + break; + } + if (multiNodeTreePickerItem != null) { multiNodeTreePicker.Add(multiNodeTreePickerItem); diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs index 7cd631d663..fee6ee8f8a 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs @@ -55,19 +55,18 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { using (_proflog.DebugDuration($"ConvertPropertyToNestedContent ({propertyType.DataType.Id})")) { - var value = (string)inter; - if (string.IsNullOrWhiteSpace(value)) return null; - - var objects = JsonConvert.DeserializeObject>(value); - if (objects.Count == 0) - return Enumerable.Empty(); - var configuration = propertyType.DataType.ConfigurationAs(); var contentTypes = configuration.ContentTypes; var elements = contentTypes.Length > 1 ? new List() : PublishedModelFactory.CreateModelList(contentTypes[0].Alias); + var value = (string)inter; + if (string.IsNullOrWhiteSpace(value)) return elements; + + var objects = JsonConvert.DeserializeObject>(value); + if (objects.Count == 0) return elements; + foreach (var sourceObject in objects) { var element = ConvertToElement(sourceObject, referenceCacheLevel, preview); diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs index b3b3919278..983d122a83 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs @@ -77,7 +77,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { var strLinkId = linkData.Link; var udiAttempt = strLinkId.TryConvertTo(); - if (udiAttempt.Success) + if (udiAttempt.Success && udiAttempt.Result != null) { var content = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(udiAttempt.Result.Guid); if (content != null) diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs index 32c3ffb2c7..74a5fb313c 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs @@ -9,6 +9,7 @@ using System.Linq; using HtmlAgilityPack; using Umbraco.Core.Cache; using Umbraco.Core.Services; +using Umbraco.Web.Macros; namespace Umbraco.Web.PropertyEditors.ValueConverters { @@ -73,7 +74,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var sourceString = source.ToString(); // ensures string is parsed for {localLink} and urls are resolved correctly - sourceString = TemplateUtilities.ParseInternalLinks(sourceString, preview); + sourceString = TemplateUtilities.ParseInternalLinks(sourceString, preview, UmbracoContext.Current); sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString); // ensure string is parsed for macros and macros are executed correctly diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs index 13e45bb286..5fe731ffb3 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs @@ -31,7 +31,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var sourceString = source.ToString(); // ensures string is parsed for {localLink} and urls are resolved correctly - sourceString = TemplateUtilities.ParseInternalLinks(sourceString, preview); + sourceString = TemplateUtilities.ParseInternalLinks(sourceString, preview, UmbracoContext.Current); sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString); return sourceString; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs index 8dd3bb8dc7..132c4b6d59 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs @@ -90,7 +90,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // determines whether a property has value public override bool HasValue(string culture = null, string segment = null) { - ContextualizeVariation(ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); var value = GetSourceValue(culture, segment); var hasValue = PropertyType.IsValue(value, PropertyValueLevel.Source); @@ -194,7 +194,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetSourceValue(string culture = null, string segment = null) { - ContextualizeVariation(ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); if (culture == "" && segment == "") return _sourceValue; @@ -206,19 +206,9 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - private void ContextualizeVariation(ref string culture, ref string segment) - { - if (culture != null && segment != null) return; - - // use context values - var publishedVariationContext = _content.VariationContextAccessor?.VariationContext; - if (culture == null) culture = _variations.VariesByCulture() ? publishedVariationContext?.Culture : ""; - if (segment == null) segment = _variations.VariesBySegment() ? publishedVariationContext?.Segment : ""; - } - public override object GetValue(string culture = null, string segment = null) { - ContextualizeVariation(ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); object value; lock (_locko) @@ -239,7 +229,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetXPathValue(string culture = null, string segment = null) { - ContextualizeVariation(ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); lock (_locko) { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 5bcb2f70d4..8786753e4f 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -303,7 +303,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var kits = _dataSource.GetAllContentSources(scope); _contentStore.SetAll(kits); sw.Stop(); - _logger.Debug("Loaded content from database ({ElapsedMilliseconds}ms)", sw.ElapsedMilliseconds); + _logger.Debug("Loaded content from database ({Duration}ms)", sw.ElapsedMilliseconds); } private void LoadContentFromLocalDbLocked(IScope scope) @@ -317,7 +317,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var kits = _localContentDb.Select(x => x.Value).OrderBy(x => x.Node.Level); _contentStore.SetAll(kits); sw.Stop(); - _logger.Debug("Loaded content from local db ({ElapsedMilliseconds}ms)", sw.ElapsedMilliseconds); + _logger.Debug("Loaded content from local db ({Duration}ms)", sw.ElapsedMilliseconds); } // keep these around - might be useful @@ -370,7 +370,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var kits = _dataSource.GetAllMediaSources(scope); _mediaStore.SetAll(kits); sw.Stop(); - _logger.Debug("Loaded media from database ({ElapsedMilliseconds}ms)", sw.ElapsedMilliseconds); + _logger.Debug("Loaded media from database ({Duration}ms)", sw.ElapsedMilliseconds); } private void LoadMediaFromLocalDbLocked(IScope scope) @@ -384,7 +384,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var kits = _localMediaDb.Select(x => x.Value); _mediaStore.SetAll(kits); sw.Stop(); - _logger.Debug("Loaded media from local db ({ElapsedMilliseconds}ms)", sw.ElapsedMilliseconds); + _logger.Debug("Loaded media from local db ({Duration}ms)", sw.ElapsedMilliseconds); } // keep these around - might be useful @@ -1196,7 +1196,7 @@ namespace Umbraco.Web.PublishedCache.NuCache foreach (var (culture, name) in names) { - cultureData[culture] = new CultureVariation { Name = name, Date = content.GetCultureDate(culture) ?? DateTime.MinValue }; + cultureData[culture] = new CultureVariation { Name = name, Date = content.GetUpdateDate(culture) ?? DateTime.MinValue }; } //the dictionary that will be serialized @@ -1277,7 +1277,7 @@ WHERE cmsContentNu.nodeId IN ( long total; do { - var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); var items = new List(); foreach (var c in descendants) { @@ -1344,7 +1344,7 @@ WHERE cmsContentNu.nodeId IN ( long total; do { - var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); var items = descendants.Select(m => GetDto(m, false)).ToArray(); db.BulkInsertRecords(items); processed += items.Length; @@ -1402,7 +1402,7 @@ WHERE cmsContentNu.nodeId IN ( long total; do { - var descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); var items = descendants.Select(m => GetDto(m, false)).ToArray(); db.BulkInsertRecords(items); processed += items.Length; diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs index 5b640f13e5..5fa89e3f7b 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs @@ -1746,7 +1746,7 @@ WHERE cmsContentXml.nodeId IN ( long total; do { - var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); const bool published = true; // contentXml contains published content! var items = descendants.Select(c => new ContentXmlDto { NodeId = c.Id, Xml = EntityXmlSerializer.Serialize(_serviceContext.ContentService, _serviceContext.DataTypeService, _serviceContext.UserService, _serviceContext.LocalizationService, _segmentProviders, c, published).ToDataString() }).ToArray(); @@ -1819,7 +1819,7 @@ WHERE cmsPreviewXml.nodeId IN ( { // .GetPagedResultsByQuery implicitely adds ({Constants.DatabaseSchema.Tables.Document}.newest = 1) which // is what we want for preview (ie latest version of a content, published or not) - var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); const bool published = true; // previewXml contains edit content! var items = descendants.Select(c => new PreviewXmlDto { @@ -1892,7 +1892,7 @@ WHERE cmsContentXml.nodeId IN ( long total; do { - var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); var items = descendants.Select(m => new ContentXmlDto { NodeId = m.Id, Xml = EntityXmlSerializer.Serialize(_serviceContext.MediaService, _serviceContext.DataTypeService, _serviceContext.UserService, _serviceContext.LocalizationService, _segmentProviders, m).ToDataString() }).ToArray(); db.BulkInsertRecords(items); @@ -1961,7 +1961,7 @@ WHERE cmsContentXml.nodeId IN ( long total; do { - var descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); var items = descendants.Select(m => new ContentXmlDto { NodeId = m.Id, Xml = EntityXmlSerializer.Serialize(_serviceContext.DataTypeService, _serviceContext.LocalizationService, m).ToDataString() }).ToArray(); db.BulkInsertRecords(items); processed += items.Length; diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 5350e05ef9..f0ddf62074 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -5,8 +5,9 @@ using System.Linq; using System.Web; using Examine; using Examine.LuceneEngine.SearchCriteria; -using Umbraco.Core.Models.PublishedContent; using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Composing; @@ -82,6 +83,45 @@ namespace Umbraco.Web // return the corresponding url segment, or null if none var cultureInfo = content.GetCulture(culture); return cultureInfo?.UrlSegment; + } + + public static bool IsAllowedTemplate(this IPublishedContent content, int templateId) + { + if (UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates == true) + return content.TemplateId == templateId; + + if (content.TemplateId != templateId && UmbracoConfig.For.UmbracoSettings().WebRouting.ValidateAlternativeTemplates == true) + { + // fixme - perfs? nothing cached here + var publishedContentContentType = Current.Services.ContentTypeService.Get(content.ContentType.Id); + if (publishedContentContentType == null) + throw new NullReferenceException("No content type returned for published content (contentType='" + content.ContentType.Id + "')"); + + return publishedContentContentType.IsAllowedTemplate(templateId); + } + + return true; + } + public static bool IsAllowedTemplate(this IPublishedContent content, string templateAlias) + { + // fixme - perfs? nothing cached here + var template = Current.Services.FileService.GetTemplate(templateAlias); + return template != null && content.IsAllowedTemplate(template.Id); + } + + #endregion + + #region IsComposedOf + + /// + /// Gets a value indicating whether the content is of a content type composed of the given alias + /// + /// The content. + /// The content type alias. + /// A value indicating whether the content is of a content type composed of a content type identified by the alias. + public static bool IsComposedOf(this IPublishedContent content, string alias) + { + return content.ContentType.CompositionAliases.Contains(alias); } #endregion @@ -146,30 +186,30 @@ namespace Umbraco.Web #region Value /// - /// Recursively the value of a content's property identified by its alias, if it exists, otherwise a default value. + /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. /// /// The content. /// The property alias. /// The variation language. /// The variation segment. - /// A value indicating whether to recurse. + /// Optional fallback strategy. /// The default value. /// The value of the content's property identified by the alias, if it exists, otherwise a default value. - /// - /// Recursively means: walking up the tree from , get the first value that can be found. - /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. - /// If no property with the specified alias exists, or if the property has no value, returns . - /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. - /// The alias is case-insensitive. - /// - public static object Value(this IPublishedContent content, string alias, string culture = null, string segment = null, object defaultValue = default, bool recurse = false) + public static object Value(this IPublishedContent content, string alias, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default) { var property = content.GetProperty(alias); + // if we have a property, and it has a value, return that value if (property != null && property.HasValue(culture, segment)) return property.GetValue(culture, segment); - return PublishedValueFallback.GetValue(content, alias, culture, segment, defaultValue, recurse); + // else let fallback try to get a value + if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value)) + return value; + + // else... if we have a property, at least let the converter return its own + // vision of 'no value' (could be an empty enumerable) - otherwise, default + return property?.GetValue(culture, segment); } #endregion @@ -177,35 +217,35 @@ namespace Umbraco.Web #region Value /// - /// Recursively gets the value of a content's property identified by its alias, converted to a specified type. + /// Gets the value of a content's property identified by its alias, converted to a specified type. /// /// The target property type. /// The content. /// The property alias. /// The variation language. /// The variation segment. + /// Optional fallback strategy. /// The default value. - /// A value indicating whether to recurse. /// The value of the content's property identified by the alias, converted to the specified type. - /// - /// Recursively means: walking up the tree from , get the first value that can be found. - /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. - /// If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns default(T). - /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. - /// The alias is case-insensitive. - /// - public static T Value(this IPublishedContent content, string alias, string culture = null, string segment = null, T defaultValue = default, bool recurse = false) + public static T Value(this IPublishedContent content, string alias, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default) { var property = content.GetProperty(alias); + // if we have a property, and it has a value, return that value if (property != null && property.HasValue(culture, segment)) return property.Value(culture, segment); - return PublishedValueFallback.GetValue(content, alias, culture, segment, defaultValue, recurse); + // else let fallback try to get a value + if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value)) + return value; + + // else... if we have a property, at least let the converter return its own + // vision of 'no value' (could be an empty enumerable) - otherwise, default + return property == null ? default : property.Value(culture, segment); } // fixme - .Value() refactoring - in progress - public static IHtmlString Value(this IPublishedContent content, string aliases, Func format, string alt = "", bool recurse = false) + public static IHtmlString Value(this IPublishedContent content, string aliases, Func format, string alt = "", int fallback = 0) { var aliasesA = aliases.Split(','); if (aliasesA.Length == 0) diff --git a/src/Umbraco.Web/PublishedContentPropertyExtension.cs b/src/Umbraco.Web/PublishedContentPropertyExtension.cs deleted file mode 100644 index fcbfc7f431..0000000000 --- a/src/Umbraco.Web/PublishedContentPropertyExtension.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Web -{ - /// - /// Provides extension methods for IPublishedProperty. - /// - public static class PublishedPropertyExtension - { - // see notes in PublishedElementExtensions - // - private static IPublishedValueFallback PublishedValueFallback => Current.PublishedValueFallback; - - #region Value - - public static object Value(this IPublishedProperty property, string culture = null, string segment = null, object defaultValue = default) - { - if (property.HasValue(culture, segment)) - return property.GetValue(culture, segment); - - return PublishedValueFallback.GetValue(property, culture, segment, defaultValue); - } - - #endregion - - #region Value - - public static T Value(this IPublishedProperty property, string culture = null, string segment = null, T defaultValue = default) - { - // for Value when defaultValue is not specified, and HasValue() is false, we still want to convert the result (see below) - // but we have no way to tell whether default value is specified or not - we could do it with overloads, but then defaultValue - // comes right after property and conflicts with culture when T is string - so we're just not doing it - if defaultValue is - // default, whether specified or not, we give a chance to the converter - // - //if (!property.HasValue(culture, segment) && 'defaultValue is explicitely specified') return defaultValue; - - // give the converter a chance to handle the default value differently - // eg for IEnumerable it may return Enumerable.Empty instead of null - - var value = property.GetValue(culture, segment); - - // if value is null (strange but why not) it still is OK to call TryConvertTo - // because it's an extension method (hence no NullRef) which will return a - // failed attempt. So, no need to care for value being null here. - - // if already the requested type, return - if (value is T variable) return variable; - - // if can convert to requested type, return - var convert = value.TryConvertTo(); - if (convert.Success) return convert.Result; - - // at that point, the code tried with the raw value - // that makes no sense because it sort of is unpredictable, - // you wouldn't know when the converters run or don't run. - // so, it's commented out now. - - // try with the raw value - //var source = property.ValueSource; - //if (source is string) source = TextValueConverterHelper.ParseStringValueSource((string)source); - //if (source is T) return (T)source; - //convert = source.TryConvertTo(); - //if (convert.Success) return convert.Result; - - return defaultValue; - } - - #endregion - } -} diff --git a/src/Umbraco.Web/PublishedElementExtensions.cs b/src/Umbraco.Web/PublishedElementExtensions.cs index 945270cb9e..cd6ede9a7c 100644 --- a/src/Umbraco.Web/PublishedElementExtensions.cs +++ b/src/Umbraco.Web/PublishedElementExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Web; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; @@ -98,6 +99,7 @@ namespace Umbraco.Web /// The property alias. /// The variation language. /// The variation segment. + /// Optional fallback strategy. /// The default value. /// The value of the content's property identified by the alias, if it exists, otherwise a default value. /// @@ -106,14 +108,21 @@ namespace Umbraco.Web /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. /// The alias is case-insensitive. /// - public static object Value(this IPublishedElement content, string alias, string culture = null, string segment = null, object defaultValue = default) + public static object Value(this IPublishedElement content, string alias, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default) { var property = content.GetProperty(alias); + // if we have a property, and it has a value, return that value if (property != null && property.HasValue(culture, segment)) return property.GetValue(culture, segment); - return PublishedValueFallback.GetValue(content, alias, culture, segment, defaultValue); + // else let fallback try to get a value + if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value)) + return value; + + // else... if we have a property, at least let the converter return its own + // vision of 'no value' (could be an empty enumerable) - otherwise, default + return property?.GetValue(culture, segment); } #endregion @@ -128,6 +137,7 @@ namespace Umbraco.Web /// The property alias. /// The variation language. /// The variation segment. + /// Optional fallback strategy. /// The default value. /// The value of the content's property identified by the alias, converted to the specified type. /// @@ -136,14 +146,21 @@ namespace Umbraco.Web /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. /// The alias is case-insensitive. /// - public static T Value(this IPublishedElement content, string alias, string culture = null, string segment = null, T defaultValue = default) + public static T Value(this IPublishedElement content, string alias, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default) { var property = content.GetProperty(alias); + // if we have a property, and it has a value, return that value if (property != null && property.HasValue(culture, segment)) return property.Value(culture, segment); - return PublishedValueFallback.GetValue(content, alias, culture, segment, defaultValue); + // else let fallback try to get a value + if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value)) + return value; + + // else... if we have a property, at least let the converter return its own + // vision of 'no value' (could be an empty enumerable) - otherwise, default + return property == null ? default : property.Value(culture, segment); } #endregion diff --git a/src/Umbraco.Web/PublishedPropertyExtension.cs b/src/Umbraco.Web/PublishedPropertyExtension.cs new file mode 100644 index 0000000000..b431f24828 --- /dev/null +++ b/src/Umbraco.Web/PublishedPropertyExtension.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web +{ + /// + /// Provides extension methods for IPublishedProperty. + /// + public static class PublishedPropertyExtension + { + // see notes in PublishedElementExtensions + // + private static IPublishedValueFallback PublishedValueFallback => Current.PublishedValueFallback; + + #region Value + + public static object Value(this IPublishedProperty property, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default) + { + if (property.HasValue(culture, segment)) + return property.GetValue(culture, segment); + + return PublishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var value) + ? value + : property.GetValue(culture, segment); // give converter a chance to return it's own vision of "no value" + } + + #endregion + + #region Value + + public static T Value(this IPublishedProperty property, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default) + { + if (property.HasValue(culture, segment)) + { + // we have a value + // try to cast or convert it + var value = property.GetValue(culture, segment); + if (value is T valueAsT) return valueAsT; + var valueConverted = value.TryConvertTo(); + if (valueConverted) return valueConverted.Result; + + // cannot cast nor convert the value, nothing we can return but 'default' + // note: we don't want to fallback in that case - would make little sense + return default; + } + + // we don't have a value, try fallback + if (PublishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var fallbackValue)) + return fallbackValue; + + // we don't have a value - neither direct nor fallback + // give a chance to the converter to return something (eg empty enumerable) + var noValue = property.GetValue(culture, segment); + if (noValue is T noValueAsT) return noValueAsT; + var noValueConverted = noValue.TryConvertTo(); + if (noValueConverted) return noValueConverted.Result; + + // cannot cast noValue nor convert it, nothing we can return but 'default' + return default; + } + + #endregion + } +} diff --git a/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs index 447d4e34af..8360ad7e38 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs @@ -3,6 +3,7 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models.PublishedContent; +using System.Globalization; namespace Umbraco.Web.Routing { @@ -53,6 +54,13 @@ namespace Umbraco.Web.Routing if (node != null) { + //if we have a node, check if we have a culture in the query string + if (frequest.UmbracoContext.HttpContext.Request.QueryString.ContainsKey("culture")) + { + //we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though + frequest.Culture = CultureInfo.GetCultureInfo(frequest.UmbracoContext.HttpContext.Request.QueryString["culture"]); + } + frequest.PublishedContent = node; _logger.Debug("Found node with id={PublishedContentId}", frequest.PublishedContent.Id); } diff --git a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs index d50951161c..ee495b6d7d 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs @@ -48,7 +48,7 @@ namespace Umbraco.Web.Routing if (redirectUrl == null) { - _logger.Debug("No match for route: '{Route}'", route); + _logger.Debug("No match for route: {Route}", route); return false; } @@ -56,11 +56,11 @@ namespace Umbraco.Web.Routing var url = content == null ? "#" : content.Url; if (url.StartsWith("#")) { - _logger.Debug("Route '{Route}' matches content {ContentId} which has no url.", route, redirectUrl.ContentId); + _logger.Debug("Route {Route} matches content {ContentId} which has no url.", route, redirectUrl.ContentId); return false; } - _logger.Debug("Route '{Route}' matches content {ContentId} with url '{Url}', redirecting.", route, content.Id, url); + _logger.Debug("Route {Route} matches content {ContentId} with url '{Url}', redirecting.", route, content.Id, url); frequest.SetRedirectPermanent(url); return true; } diff --git a/src/Umbraco.Web/Routing/ContentFinderByUrl.cs b/src/Umbraco.Web/Routing/ContentFinderByUrl.cs index a3c54406a8..94b2b9dbf2 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByUrl.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByUrl.cs @@ -46,9 +46,9 @@ namespace Umbraco.Web.Routing { if (docreq == null) throw new System.ArgumentNullException(nameof(docreq)); - Logger.Debug("Test route '{Route}'", route); + Logger.Debug("Test route {Route}", route); - var node = docreq.UmbracoContext.ContentCache.GetByRoute(route, culture: docreq.Culture?.Name); + var node = docreq.UmbracoContext.ContentCache.GetByRoute(docreq.UmbracoContext.InPreviewMode, route, culture: docreq.Culture?.Name); if (node != null) { docreq.PublishedContent = node; diff --git a/src/Umbraco.Web/Routing/ContentFinderByUrlAndTemplate.cs b/src/Umbraco.Web/Routing/ContentFinderByUrlAndTemplate.cs index 1d1661966f..1e86b40a79 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByUrlAndTemplate.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByUrlAndTemplate.cs @@ -40,34 +40,49 @@ namespace Umbraco.Web.Routing if (frequest.HasDomain) path = DomainHelper.PathRelativeToDomain(frequest.Domain.Uri, path); - if (path != "/") // no template if "/" - { - var pos = path.LastIndexOf('/'); - var templateAlias = path.Substring(pos + 1); - path = pos == 0 ? "/" : path.Substring(0, pos); - - var template = _fileService.GetTemplate(templateAlias); - if (template != null) - { - Logger.Debug("Valid template: '{TemplateAlias}'", templateAlias); - - var route = frequest.HasDomain ? (frequest.Domain.ContentId.ToString() + path) : path; - node = FindContent(frequest, route); - - if (UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates == false && node != null) - frequest.TemplateModel = template; - } - else - { - Logger.Debug("Not a valid template: '{TemplateAlias}'", templateAlias); - } - } - else + // no template if "/" + if (path == "/") { Logger.Debug("No template in path '/'"); + return false; } - return node != null; + // look for template in last position + var pos = path.LastIndexOf('/'); + var templateAlias = path.Substring(pos + 1); + path = pos == 0 ? "/" : path.Substring(0, pos); + + var template = _fileService.GetTemplate(templateAlias); + + if (template == null) + { + Logger.Debug("Not a valid template: '{TemplateAlias}'", templateAlias); + return false; + } + + Logger.Debug("Valid template: '{TemplateAlias}'", templateAlias); + + // look for node corresponding to the rest of the route + var route = frequest.HasDomain ? (frequest.Domain.ContentId + path) : path; + node = FindContent(frequest, route); // also assigns to published request + + if (node == null) + { + Logger.Debug("Not a valid route to node: '{Route}'", route); + return false; + } + + // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings + if (!node.IsAllowedTemplate(template.Id)) + { + Logger.Warn("Alternative template '{TemplateAlias}' is not allowed on node {NodeId}.", template.Alias, node.Id); + frequest.PublishedContent = null; // clear + return false; + } + + // got it + frequest.TemplateModel = template; + return true; } } } diff --git a/src/Umbraco.Web/Routing/LegacyRequestInitializer.cs b/src/Umbraco.Web/Routing/LegacyRequestInitializer.cs deleted file mode 100644 index d84ff25d73..0000000000 --- a/src/Umbraco.Web/Routing/LegacyRequestInitializer.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Web; - -namespace Umbraco.Web.Routing -{ - /// - /// A legacy class for old style handling of URL requests - /// - internal class LegacyRequestInitializer - { - private readonly Uri _requestUrl; - private readonly HttpContextBase _httpContext; - - public LegacyRequestInitializer(Uri requestUrl, HttpContextBase httpContext) - { - _requestUrl = requestUrl; - _httpContext = httpContext; - } - - public void InitializeRequest() - { - var uri = _requestUrl; - - // legacy - umbOriginalUrl used by default.aspx to rewritepath so forms are happy - // legacy - umbOriginalUrl used by presentation/umbraco/urlRewriter/UrlRewriterFormWriter which handles
> getRolesForLogin = null) { - _webRoutingSection = webRoutingSection ?? throw new ArgumentNullException(nameof(webRoutingSection)); // fixme usage? + _webRoutingSection = webRoutingSection ?? throw new ArgumentNullException(nameof(webRoutingSection)); _contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders)); _contentLastChanceFinder = contentLastChanceFinder ?? throw new ArgumentNullException(nameof(contentLastChanceFinder)); _services = services ?? throw new ArgumentNullException(nameof(services)); @@ -212,7 +212,6 @@ namespace Umbraco.Web.Routing frequest.UmbracoPage = new page(frequest); // used by many legacy objects - frequest.UmbracoContext.HttpContext.Items["pageID"] = frequest.PublishedContent.Id; frequest.UmbracoContext.HttpContext.Items["pageElements"] = frequest.UmbracoPage.Elements; return true; @@ -258,8 +257,7 @@ namespace Umbraco.Web.Routing // handlers like default.aspx will want it and most macros currently need it request.UmbracoPage = new page(request); - // these two are used by many legacy objects - request.UmbracoContext.HttpContext.Items["pageID"] = request.PublishedContent.Id; + // this is used by many legacy objects request.UmbracoContext.HttpContext.Items["pageElements"] = request.UmbracoPage.Elements; } @@ -277,7 +275,7 @@ namespace Umbraco.Web.Routing // note - we are not handling schemes nor ports here. - _logger.Debug("{TracePrefix}Uri='{RequestUri}'", tracePrefix, request.Uri); + _logger.Debug("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri); var domainsCache = request.UmbracoContext.PublishedSnapshot.Domains; var domains = domainsCache.GetAll(includeWildcards: false).ToList(); @@ -314,7 +312,7 @@ namespace Umbraco.Web.Routing if (domainAndUri != null) { // matching an existing domain - _logger.Debug("{TracePrefix}Matches domain='{Domain}', rootId={RootContentId}, culture='{Culture}'", tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture); + _logger.Debug("{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture); request.Domain = domainAndUri; request.Culture = domainAndUri.Culture; @@ -334,7 +332,7 @@ namespace Umbraco.Web.Routing request.Culture = defaultCulture == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultCulture); } - _logger.Debug("{TracePrefix}Culture='{CultureName}'", tracePrefix, request.Culture.Name); + _logger.Debug("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture.Name); return request.Domain != null; } @@ -350,7 +348,7 @@ namespace Umbraco.Web.Routing return; var nodePath = request.PublishedContent.Path; - _logger.Debug("{TracePrefix}Path='{NodePath}'", tracePrefix, nodePath); + _logger.Debug("{TracePrefix}Path={NodePath}", tracePrefix, nodePath); var rootNodeId = request.HasDomain ? request.Domain.ContentId : (int?)null; var domain = DomainHelper.FindWildcardDomainInPath(request.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId); @@ -358,7 +356,7 @@ namespace Umbraco.Web.Routing if (domain != null) { request.Culture = domain.Culture; - _logger.Debug("{TracePrefix}Got domain on node {DomainContentId}, set culture to '{CultureName}'", tracePrefix, domain.ContentId, request.Culture.Name); + _logger.Debug("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture.Name); } else { @@ -435,7 +433,7 @@ namespace Umbraco.Web.Routing /// A value indicating whether a document and template were found. private void FindPublishedContentAndTemplate(PublishedRequest request) { - _logger.Debug("FindPublishedContentAndTemplate: Path='{UriAbsolutePath}'", request.Uri.AbsolutePath); + _logger.Debug("FindPublishedContentAndTemplate: Path={UriAbsolutePath}", request.Uri.AbsolutePath); // run the document finders FindPublishedContent(request); @@ -541,7 +539,7 @@ namespace Umbraco.Web.Routing if (i == maxLoop || j == maxLoop) { - _logger.Debug("HandlePublishedContent: Looks like we're running into an infinite loop, abort"); + _logger.Debug("HandlePublishedContent: Looks like we are running into an infinite loop, abort"); request.PublishedContent = null; } @@ -679,9 +677,8 @@ namespace Umbraco.Web.Routing // only if the published content is the initial once, else the alternate template // does not apply // + optionnally, apply the alternate template on internal redirects - var useAltTemplate = _webRoutingSection.DisableAlternativeTemplates == false - && (request.IsInitialPublishedContent - || (_webRoutingSection.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent)); + var useAltTemplate = request.IsInitialPublishedContent + || (_webRoutingSection.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent); var altTemplate = useAltTemplate ? request.UmbracoContext.HttpContext.Request[Constants.Conventions.Url.AltTemplate] : null; @@ -694,28 +691,15 @@ namespace Umbraco.Web.Routing if (request.HasTemplate) { - _logger.Debug("{0}Has a template already, and no alternate template."); + _logger.Debug("FindTemplate: Has a template already, and no alternate template."); return; } - // TODO: When we remove the need for a database for templates, then this id should be irrelavent, + // TODO: When we remove the need for a database for templates, then this id should be irrelevant, // not sure how were going to do this nicely. var templateId = request.PublishedContent.TemplateId; - - if (templateId > 0) - { - _logger.Debug("FindTemplate: Look for template id={TemplateId}", templateId); - var template = _services.FileService.GetTemplate(templateId); - if (template == null) - throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render"); - request.TemplateModel = template; - _logger.Debug("FindTemplate: Got template id={TemplateId} alias='{TemplateAlias}'", template.Id, template.Alias); - } - else - { - _logger.Debug("FindTemplate: No specified template."); - } + request.TemplateModel = GetTemplateModel(templateId); } else { @@ -726,18 +710,32 @@ namespace Umbraco.Web.Routing // ignore if the alias does not match - just trace if (request.HasTemplate) - _logger.Debug("FindTemplate: Has a template already, but also an alternate template."); - _logger.Debug("FindTemplate: Look for alternate template alias='{AltTemplate}'", altTemplate); + _logger.Debug("FindTemplate: Has a template already, but also an alternative template."); + _logger.Debug("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate); - var template = _services.FileService.GetTemplate(altTemplate); - if (template != null) + // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings + if (request.PublishedContent.IsAllowedTemplate(altTemplate)) { - request.TemplateModel = template; - _logger.Debug("FindTemplate: Got template id={TemplateId} alias='{TemplateAlias}'", template.Id, template.Alias); + // allowed, use + var template = _services.FileService.GetTemplate(altTemplate); + + if (template != null) + { + request.TemplateModel = template; + _logger.Debug("FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); + } + else + { + _logger.Debug("FindTemplate: The alternative template with alias={AltTemplate} does not exist, ignoring.", altTemplate); + } } else { - _logger.Debug("FindTemplate: The template with alias='{AltTemplate}' does not exist, ignoring.", altTemplate); + _logger.Warn("FindTemplate: Alternative template {TemplateAlias} is not allowed on node {NodeId}, ignoring.", altTemplate, request.PublishedContent.Id); + + // no allowed, back to default + var templateId = request.PublishedContent.TemplateId; + request.TemplateModel = GetTemplateModel(templateId); } } @@ -756,10 +754,27 @@ namespace Umbraco.Web.Routing } else { - _logger.Debug("FindTemplate: Running with template id={TemplateId} alias='{TemplateAlias}'", request.TemplateModel.Id, request.TemplateModel.Alias); + _logger.Debug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", request.TemplateModel.Id, request.TemplateModel.Alias); } } + private ITemplate GetTemplateModel(int templateId) + { + if (templateId <= 0) + { + _logger.Debug("GetTemplateModel: No template."); + return null; + } + + _logger.Debug("GetTemplateModel: Get template id={TemplateId}", templateId); + + var template = _services.FileService.GetTemplate(templateId); + if (template == null) + throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render."); + _logger.Debug("GetTemplateModel: Got template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); + return template; + } + /// /// Follows external redirection through umbracoRedirect document property. /// diff --git a/src/Umbraco.Web/Routing/UrlInfo.cs b/src/Umbraco.Web/Routing/UrlInfo.cs index cd77bb7006..01dbe4a0e1 100644 --- a/src/Umbraco.Web/Routing/UrlInfo.cs +++ b/src/Umbraco.Web/Routing/UrlInfo.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Routing /// /// Initializes a new instance of the class. /// - private UrlInfo(string text, bool isUrl, string culture) + public UrlInfo(string text, bool isUrl, string culture) { IsUrl = isUrl; Text = text; diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index f37b0fe896..36c3ba5533 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -190,8 +190,16 @@ namespace Umbraco.Web.Routing // this the ONLY place where we deal with default culture - IUrlProvider always receive a culture // be nice with tests, assume things can be null, ultimately fall back to invariant - if (culture == null) - culture = _variationContextAccessor?.VariationContext?.Culture ?? ""; + // (but only for variant content of course) + if (content.ContentType.VariesByCulture()) + { + if (culture == null) + culture = _variationContextAccessor?.VariationContext?.Culture ?? ""; + } + else + { + culture = null; + } if (current == null) current = _umbracoContext.CleanedUmbracoUrl; diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs index c43fec6382..280fcce352 100644 --- a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs +++ b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs @@ -31,11 +31,13 @@ using Umbraco.Core.Runtime; using Umbraco.Core.Services; using Umbraco.Web.Cache; using Umbraco.Web.Composing.Composers; +using Umbraco.Web.ContentApps; using Umbraco.Web.Dictionary; using Umbraco.Web.Editors; using Umbraco.Web.Features; using Umbraco.Web.HealthCheck; using Umbraco.Web.Install; +using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.PublishedContent; using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; @@ -200,6 +202,12 @@ namespace Umbraco.Web.Runtime // register properties fallback composition.Container.RegisterSingleton(); + + // register known content apps + composition.Container.RegisterCollectionBuilder() + .Append() + .Append() + .Append(); } internal void Initialize( diff --git a/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs b/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs index 68e306462f..89fba7717d 100644 --- a/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs +++ b/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs @@ -37,8 +37,8 @@ namespace Umbraco.Web.Scheduling switch (_runtimeState.ServerRole) { - case ServerRole.Slave: - _logger.Debug("Does not run on slave servers."); + case ServerRole.Replica: + _logger.Debug("Does not run on replica servers."); return true; // DO repeat, server role can change case ServerRole.Unknown: _logger.Debug("Does not run on servers with unknown role."); diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs index cdc011c52d..24f6775166 100644 --- a/src/Umbraco.Web/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs @@ -28,11 +28,11 @@ namespace Umbraco.Web.Scheduling public override async Task PerformRunAsync(CancellationToken token) { - // not on slaves nor unknown role servers + // not on replicas nor unknown role servers switch (_runtime.ServerRole) { - case ServerRole.Slave: - _logger.Debug("Does not run on slave servers."); + case ServerRole.Replica: + _logger.Debug("Does not run on replica servers."); return true; // role may change! case ServerRole.Unknown: _logger.Debug("Does not run on servers with unknown role."); diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs index 5fcdd4d6dc..ae73da04c8 100644 --- a/src/Umbraco.Web/Scheduling/LogScrubber.cs +++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs @@ -68,8 +68,8 @@ namespace Umbraco.Web.Scheduling { switch (_runtime.ServerRole) { - case ServerRole.Slave: - _logger.Debug("Does not run on slave servers."); + case ServerRole.Replica: + _logger.Debug("Does not run on replica servers."); return true; // DO repeat, server role can change case ServerRole.Unknown: _logger.Debug("Does not run on servers with unknown role."); diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index ae2031dac6..471eb213c0 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -31,8 +31,8 @@ namespace Umbraco.Web.Scheduling switch (_runtime.ServerRole) { - case ServerRole.Slave: - _logger.Debug("Does not run on slave servers."); + case ServerRole.Replica: + _logger.Debug("Does not run on replica servers."); return true; // DO repeat, server role can change case ServerRole.Unknown: _logger.Debug("Does not run on servers with unknown role."); diff --git a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs index 9644ad864c..fdaa4dfd6d 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs @@ -17,6 +17,7 @@ namespace Umbraco.Web.Scheduling internal class ScheduledTasks : RecurringTaskBase { + private static HttpClient _httpClient; private readonly IRuntimeState _runtime; private readonly IUmbracoSettingsSection _settings; private readonly ILogger _logger; @@ -65,35 +66,36 @@ namespace Umbraco.Web.Scheduling private async Task GetTaskByHttpAync(string url, CancellationToken token) { - using (var wc = new HttpClient()) + if (_httpClient == null) + _httpClient = new HttpClient + { + BaseAddress = _runtime.ApplicationUrl + }; + + var request = new HttpRequestMessage(HttpMethod.Get, url); + + //TODO: pass custom the authorization header, currently these aren't really secured! + //request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext); + + try { - // url could be relative, so better set a base url for the http client - wc.BaseAddress = _runtime.ApplicationUrl; - - var request = new HttpRequestMessage(HttpMethod.Get, url); - - //TODO: pass custom the authorization header, currently these aren't really secured! - //request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext); - - try - { - var result = await wc.SendAsync(request, token).ConfigureAwait(false); // ConfigureAwait(false) is recommended? http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html - return result.StatusCode == HttpStatusCode.OK; - } - catch (Exception ex) - { - _logger.Error(ex, "An error occurred calling web task for url: {Url}", url); - } - return false; + var result = await _httpClient.SendAsync(request, token).ConfigureAwait(false); // ConfigureAwait(false) is recommended? http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html + return result.StatusCode == HttpStatusCode.OK; } + catch (Exception ex) + { + _logger.Error(ex, "An error occurred calling web task for url: {Url}", url); + + } + return false; } public override async Task PerformRunAsync(CancellationToken token) { switch (_runtime.ServerRole) { - case ServerRole.Slave: - _logger.Debug("Does not run on slave servers."); + case ServerRole.Replica: + _logger.Debug("Does not run on replica servers."); return true; // DO repeat, server role can change case ServerRole.Unknown: _logger.Debug("Does not run on servers with unknown role."); diff --git a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs b/src/Umbraco.Web/Scheduling/SchedulerComponent.cs index 12f85c49ba..d9b2893c62 100644 --- a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs +++ b/src/Umbraco.Web/Scheduling/SchedulerComponent.cs @@ -117,7 +117,7 @@ namespace Umbraco.Web.Scheduling private IBackgroundTask RegisterScheduledPublishing() { // scheduled publishing/unpublishing - // install on all, will only run on non-slaves servers + // install on all, will only run on non-replica servers var task = new ScheduledPublishing(_publishingRunner, 60000, 60000, _runtime, _contentService, _logger, _userService); _publishingRunner.TryAdd(task); return task; @@ -158,7 +158,7 @@ namespace Umbraco.Web.Scheduling private IBackgroundTask RegisterLogScrubber(IUmbracoSettingsSection settings) { // log scrubbing - // install on all, will only run on non-slaves servers + // install on all, will only run on non-replica servers var task = new LogScrubber(_scrubberRunner, 60000, LogScrubber.GetLogScrubbingInterval(settings, _logger), _runtime, _auditService, settings, _scopeProvider, _logger, _proflog); _scrubberRunner.TryAdd(task); return task; diff --git a/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs b/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs similarity index 93% rename from src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs rename to src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs index 41028e38b3..3ac74bd7c0 100644 --- a/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs +++ b/src/Umbraco.Web/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs @@ -4,8 +4,9 @@ using System.DirectoryServices.AccountManagement; using System.Threading.Tasks; using Umbraco.Core.Models.Identity; -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { + //TODO: This relies on an assembly that is not .NET Standard :( public class ActiveDirectoryBackOfficeUserPasswordChecker : IBackOfficeUserPasswordChecker { public virtual string ActiveDirectoryDomain diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/AppBuilderExtensions.cs similarity index 99% rename from src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs rename to src/Umbraco.Web/Security/AppBuilderExtensions.cs index 48b505c80c..ddcf87e9c7 100644 --- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/AppBuilderExtensions.cs @@ -2,7 +2,6 @@ using System.Threading; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; -using Microsoft.AspNet.SignalR; using Microsoft.Owin; using Microsoft.Owin.Extensions; using Microsoft.Owin.Logging; @@ -20,7 +19,7 @@ using Umbraco.Core.Services; using Umbraco.Web.Composing; using Constants = Umbraco.Core.Constants; -namespace Umbraco.Web.Security.Identity +namespace Umbraco.Web.Security { /// /// Provides security/identity extension methods to IAppBuilder. @@ -167,7 +166,7 @@ namespace Umbraco.Web.Security.Identity OnValidateIdentity = SecurityStampValidator .OnValidateIdentity( TimeSpan.FromMinutes(30), - (manager, user) => user.GenerateUserIdentityAsync(manager), + (manager, user) => manager.GenerateUserIdentityAsync(user), identity => identity.GetUserId()), }; diff --git a/src/Umbraco.Web/Security/Identity/AuthenticationManagerExtensions.cs b/src/Umbraco.Web/Security/AuthenticationManagerExtensions.cs similarity index 98% rename from src/Umbraco.Web/Security/Identity/AuthenticationManagerExtensions.cs rename to src/Umbraco.Web/Security/AuthenticationManagerExtensions.cs index 4dfa5a7f5b..5da9c77d6b 100644 --- a/src/Umbraco.Web/Security/Identity/AuthenticationManagerExtensions.cs +++ b/src/Umbraco.Web/Security/AuthenticationManagerExtensions.cs @@ -5,7 +5,7 @@ using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security; -namespace Umbraco.Web.Security.Identity +namespace Umbraco.Web.Security { public static class AuthenticationManagerExtensions { diff --git a/src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs b/src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs similarity index 98% rename from src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs rename to src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs index 20f93f44a6..33aabbaf94 100644 --- a/src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs +++ b/src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs @@ -2,11 +2,10 @@ using Microsoft.Owin; using Microsoft.Owin.Security; using Umbraco.Core; -using Umbraco.Core.Logging; using Umbraco.Core.Composing; using Umbraco.Core.Exceptions; -namespace Umbraco.Web.Security.Identity +namespace Umbraco.Web.Security { public static class AuthenticationOptionsExtensions { diff --git a/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs b/src/Umbraco.Web/Security/BackOfficeClaimsIdentityFactory.cs similarity index 94% rename from src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs rename to src/Umbraco.Web/Security/BackOfficeClaimsIdentityFactory.cs index 490e667c17..fbc24d2cd9 100644 --- a/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs +++ b/src/Umbraco.Web/Security/BackOfficeClaimsIdentityFactory.cs @@ -4,8 +4,10 @@ using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNet.Identity; using Umbraco.Core.Models.Identity; +using Umbraco.Core.Security; +using Constants = Umbraco.Core.Constants; -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { public class BackOfficeClaimsIdentityFactory : ClaimsIdentityFactory where T: BackOfficeIdentityUser diff --git a/src/Umbraco.Core/Security/BackOfficeCookieAuthenticationProvider.cs b/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs similarity index 92% rename from src/Umbraco.Core/Security/BackOfficeCookieAuthenticationProvider.cs rename to src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs index 4d6a4fdaeb..e12e2ac4f8 100644 --- a/src/Umbraco.Core/Security/BackOfficeCookieAuthenticationProvider.cs +++ b/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs @@ -8,11 +8,13 @@ using System.Threading.Tasks; using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Security; using Umbraco.Core.Services; -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { public class BackOfficeCookieAuthenticationProvider : CookieAuthenticationProvider { @@ -50,7 +52,7 @@ namespace Umbraco.Core.Security if (context?.OwinContext?.Authentication?.User?.Identity != null) { var claimsIdentity = context.OwinContext.Authentication.User.Identity as ClaimsIdentity; - var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType); + var sessionId = claimsIdentity.FindFirstValue(Core.Constants.Security.SessionIdClaimType); if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out var guidSession)) { _userService.ClearLoginSession(guidSession); @@ -70,12 +72,12 @@ namespace Umbraco.Core.Security Expires = DateTime.Now.AddYears(-1), Path = "/" }); - context.Response.Cookies.Append(Constants.Web.PreviewCookieName, "", new CookieOptions + context.Response.Cookies.Append(Core.Constants.Web.PreviewCookieName, "", new CookieOptions { Expires = DateTime.Now.AddYears(-1), Path = "/" }); - context.Response.Cookies.Append(Constants.Security.BackOfficeExternalCookieName, "", new CookieOptions + context.Response.Cookies.Append(Core.Constants.Security.BackOfficeExternalCookieName, "", new CookieOptions { Expires = DateTime.Now.AddYears(-1), Path = "/" diff --git a/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs b/src/Umbraco.Web/Security/BackOfficeCookieManager.cs similarity index 99% rename from src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs rename to src/Umbraco.Web/Security/BackOfficeCookieManager.cs index 204cd18072..5da86204f3 100644 --- a/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeCookieManager.cs @@ -8,7 +8,7 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Security; -namespace Umbraco.Web.Security.Identity +namespace Umbraco.Web.Security { /// /// A custom cookie manager that is used to read the cookie from the request. diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs similarity index 98% rename from src/Umbraco.Core/Security/BackOfficeSignInManager.cs rename to src/Umbraco.Web/Security/BackOfficeSignInManager.cs index d156871697..b711fe76cf 100644 --- a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs @@ -9,8 +9,10 @@ using Microsoft.Owin.Logging; using Microsoft.Owin.Security; using Umbraco.Core.Configuration; using Umbraco.Core.Models.Identity; +using Umbraco.Core.Security; +using Constants = Umbraco.Core.Constants; -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { //TODO: In v8 we need to change this to use an int? nullable TKey instead, see notes against overridden TwoFactorSignInAsync public class BackOfficeSignInManager : SignInManager @@ -32,7 +34,7 @@ namespace Umbraco.Core.Security public override Task CreateUserIdentityAsync(BackOfficeIdentityUser user) { - return user.GenerateUserIdentityAsync((BackOfficeUserManager)UserManager); + return ((BackOfficeUserManager)UserManager).GenerateUserIdentityAsync(user); } public static BackOfficeSignInManager Create(IdentityFactoryOptions options, IOwinContext context, IGlobalSettings globalSettings, ILogger logger) diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Web/Security/BackOfficeUserManager.cs similarity index 98% rename from src/Umbraco.Core/Security/BackOfficeUserManager.cs rename to src/Umbraco.Web/Security/BackOfficeUserManager.cs index c4406054e6..ce51f82c43 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeUserManager.cs @@ -1,23 +1,20 @@ using System; -using System.ComponentModel; -using System.Configuration.Provider; using System.Linq; -using System.Text; +using System.Security.Claims; using System.Threading.Tasks; -using System.Web.Security; using System.Web; +using System.Web.Security; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security.DataProtection; -using Umbraco.Core.Composing; -using Umbraco.Core.Auditing; +using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models.Identity; -using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; using Umbraco.Core.Services; -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { /// /// Default back office user manager @@ -154,7 +151,15 @@ namespace Umbraco.Core.Security get { return false; } } #endregion - + + public virtual async Task GenerateUserIdentityAsync(T user) + { + // NOTE the authenticationType must match the umbraco one + // defined in CookieAuthenticationOptions.AuthenticationType + var userIdentity = await CreateIdentityAsync(user, Core.Constants.Security.BackOfficeAuthenticationType); + return userIdentity; + } + /// /// Initializes the user manager with the correct options /// @@ -186,7 +191,10 @@ namespace Umbraco.Core.Security if (dataProtectionProvider != null) { - manager.UserTokenProvider = new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")); + manager.UserTokenProvider = new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")) + { + TokenLifespan = TimeSpan.FromDays(3) + }; } manager.UserLockoutEnabledByDefault = true; @@ -698,6 +706,7 @@ namespace Umbraco.Core.Security var httpContext = HttpContext.Current == null ? (HttpContextBase)null : new HttpContextWrapper(HttpContext.Current); return httpContext.GetCurrentRequestIpAddress(); } + } } diff --git a/src/Umbraco.Core/Security/BackOfficeUserManagerMarker.cs b/src/Umbraco.Web/Security/BackOfficeUserManagerMarker.cs similarity index 95% rename from src/Umbraco.Core/Security/BackOfficeUserManagerMarker.cs rename to src/Umbraco.Web/Security/BackOfficeUserManagerMarker.cs index 300350072e..2f5a160198 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserManagerMarker.cs +++ b/src/Umbraco.Web/Security/BackOfficeUserManagerMarker.cs @@ -2,8 +2,9 @@ using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Umbraco.Core.Models.Identity; +using Umbraco.Core.Security; -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { /// /// This class is only here due to the fact that IOwinContext Get / Set only work in generics, if they worked diff --git a/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs b/src/Umbraco.Web/Security/BackOfficeUserPasswordCheckerResult.cs similarity index 88% rename from src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs rename to src/Umbraco.Web/Security/BackOfficeUserPasswordCheckerResult.cs index 365a9941b2..5681bb4766 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs +++ b/src/Umbraco.Web/Security/BackOfficeUserPasswordCheckerResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { /// /// The result returned from the IBackOfficeUserPasswordChecker diff --git a/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs similarity index 95% rename from src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs rename to src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs index fd074b9e96..a143233ff2 100644 --- a/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs +++ b/src/Umbraco.Web/Security/ExternalSignInAutoLinkOptions.cs @@ -1,13 +1,9 @@ using System; -using System.ComponentModel; using Microsoft.AspNet.Identity.Owin; -using Microsoft.Owin; -using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Exceptions; using Umbraco.Core.Models.Identity; -namespace Umbraco.Web.Security.Identity +namespace Umbraco.Web.Security { /// /// Options used to configure auto-linking external OAuth providers diff --git a/src/Umbraco.Web/Security/Identity/FixWindowsAuthMiddlware.cs b/src/Umbraco.Web/Security/FixWindowsAuthMiddlware.cs similarity index 98% rename from src/Umbraco.Web/Security/Identity/FixWindowsAuthMiddlware.cs rename to src/Umbraco.Web/Security/FixWindowsAuthMiddlware.cs index aea1b14ecd..30038e1f31 100644 --- a/src/Umbraco.Web/Security/Identity/FixWindowsAuthMiddlware.cs +++ b/src/Umbraco.Web/Security/FixWindowsAuthMiddlware.cs @@ -6,7 +6,7 @@ using Microsoft.Owin; using Umbraco.Core; using Umbraco.Core.Security; -namespace Umbraco.Web.Security.Identity +namespace Umbraco.Web.Security { /// /// This is used to inspect the request to see if 2 x identities are assigned: A windows one and a back office one. diff --git a/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs b/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationHandler.cs similarity index 98% rename from src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs rename to src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationHandler.cs index f3c7013701..896a0ca63e 100644 --- a/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs +++ b/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationHandler.cs @@ -1,12 +1,11 @@ -using System; -using Umbraco.Core; -using System.Threading.Tasks; -using Umbraco.Core.Security; +using System.Threading.Tasks; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.Infrastructure; +using Umbraco.Core; +using Umbraco.Core.Security; -namespace Umbraco.Web.Security.Identity +namespace Umbraco.Web.Security { /// /// If a flag is set on the context to force renew the ticket, this will do it diff --git a/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationMiddleware.cs b/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationMiddleware.cs similarity index 96% rename from src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationMiddleware.cs rename to src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationMiddleware.cs index 02d27eccbb..1b72412f1c 100644 --- a/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationMiddleware.cs +++ b/src/Umbraco.Web/Security/ForceRenewalCookieAuthenticationMiddleware.cs @@ -3,7 +3,7 @@ using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.Infrastructure; using Owin; -namespace Umbraco.Web.Security.Identity +namespace Umbraco.Web.Security { /// /// This middleware is used simply to force renew the auth ticket if a flag to do so is found in the request diff --git a/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs b/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs similarity index 98% rename from src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs rename to src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs index 25482f17af..dace264996 100644 --- a/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs +++ b/src/Umbraco.Web/Security/GetUserSecondsMiddleWare.cs @@ -3,16 +3,14 @@ using System.Diagnostics; using System.Globalization; using System.Threading.Tasks; using System.Web; -using System.Web.Security; using Microsoft.Owin; using Microsoft.Owin.Logging; -using Microsoft.Owin.Security.Cookies; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Security; -namespace Umbraco.Web.Security.Identity +namespace Umbraco.Web.Security { /// /// Custom middleware to return the remaining seconds the user has before they are logged out diff --git a/src/Umbraco.Core/Security/IBackOfficeUserManagerMarker.cs b/src/Umbraco.Web/Security/IBackOfficeUserManagerMarker.cs similarity index 94% rename from src/Umbraco.Core/Security/IBackOfficeUserManagerMarker.cs rename to src/Umbraco.Web/Security/IBackOfficeUserManagerMarker.cs index 5d3154ad2a..53e72ef159 100644 --- a/src/Umbraco.Core/Security/IBackOfficeUserManagerMarker.cs +++ b/src/Umbraco.Web/Security/IBackOfficeUserManagerMarker.cs @@ -1,7 +1,7 @@ using Microsoft.Owin; using Umbraco.Core.Models.Identity; -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { /// /// This interface is only here due to the fact that IOwinContext Get / Set only work in generics, if they worked diff --git a/src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs b/src/Umbraco.Web/Security/IBackOfficeUserPasswordChecker.cs similarity index 97% rename from src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs rename to src/Umbraco.Web/Security/IBackOfficeUserPasswordChecker.cs index 210e3109ab..c3ce15b487 100644 --- a/src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs +++ b/src/Umbraco.Web/Security/IBackOfficeUserPasswordChecker.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using Umbraco.Core.Models.Identity; -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { /// /// Used by the BackOfficeUserManager to check the username/password which allows for developers to more easily diff --git a/src/Umbraco.Web/Security/Identity/IUmbracoBackOfficeTwoFactorOptions.cs b/src/Umbraco.Web/Security/IUmbracoBackOfficeTwoFactorOptions.cs similarity index 90% rename from src/Umbraco.Web/Security/Identity/IUmbracoBackOfficeTwoFactorOptions.cs rename to src/Umbraco.Web/Security/IUmbracoBackOfficeTwoFactorOptions.cs index 8a1ffc4614..acd49ec5e0 100644 --- a/src/Umbraco.Web/Security/Identity/IUmbracoBackOfficeTwoFactorOptions.cs +++ b/src/Umbraco.Web/Security/IUmbracoBackOfficeTwoFactorOptions.cs @@ -1,6 +1,6 @@ using Microsoft.Owin; -namespace Umbraco.Web.Security.Identity +namespace Umbraco.Web.Security { /// /// Used to display a custom view in the back office if developers choose to implement their own custom 2 factor authentication diff --git a/src/Umbraco.Core/Auditing/IdentityAuditEventArgs.cs b/src/Umbraco.Web/Security/IdentityAuditEventArgs.cs similarity index 98% rename from src/Umbraco.Core/Auditing/IdentityAuditEventArgs.cs rename to src/Umbraco.Web/Security/IdentityAuditEventArgs.cs index ff335434ab..4756390d06 100644 --- a/src/Umbraco.Core/Auditing/IdentityAuditEventArgs.cs +++ b/src/Umbraco.Web/Security/IdentityAuditEventArgs.cs @@ -1,10 +1,8 @@ using System; -using System.ComponentModel; using System.Threading; -using System.Web; using Umbraco.Core.Security; -namespace Umbraco.Core.Auditing +namespace Umbraco.Web.Security { /// /// This class is used by events raised from hthe BackofficeUserManager diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index b632173669..eb4c24aabc 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -618,10 +618,15 @@ namespace Umbraco.Web.Security var provider = _membershipProvider; string username; + if (provider.IsUmbracoMembershipProvider()) { var member = GetCurrentPersistedMember(); + // If a member could not be resolved from the provider, we are clearly not authorized and can break right here + if (member == null) + return false; username = member.Username; + // If types defined, check member is of one of those types var allowTypesList = allowTypes as IList ?? allowTypes.ToList(); if (allowTypesList.Any(allowType => allowType != string.Empty)) @@ -640,6 +645,9 @@ namespace Umbraco.Web.Security else { var member = provider.GetCurrentUser(); + // If a member could not be resolved from the provider, we are clearly not authorized and can break right here + if (member == null) + return false; username = member.UserName; } diff --git a/src/Umbraco.Web/Security/OwinExtensions.cs b/src/Umbraco.Web/Security/OwinExtensions.cs deleted file mode 100644 index 80ba0acfa4..0000000000 --- a/src/Umbraco.Web/Security/OwinExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Web; -using Microsoft.AspNet.Identity.Owin; -using Microsoft.Owin; -using Microsoft.Owin.Security; -using Umbraco.Core; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Security; -using Umbraco.Web.Security.Identity; - -namespace Umbraco.Web.Security -{ - internal static class OwinExtensions - { - /// - /// Gets the for the Umbraco back office cookie - /// - /// - /// - internal static ISecureDataFormat GetUmbracoAuthTicketDataProtector(this IOwinContext owinContext) - { - var found = owinContext.Get(); - return found?.Protector; - } - - } -} diff --git a/src/Umbraco.Web/Security/Identity/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web/Security/PreviewAuthenticationMiddleware.cs similarity index 85% rename from src/Umbraco.Web/Security/Identity/PreviewAuthenticationMiddleware.cs rename to src/Umbraco.Web/Security/PreviewAuthenticationMiddleware.cs index da3e74ab82..ccb2a81e51 100644 --- a/src/Umbraco.Web/Security/Identity/PreviewAuthenticationMiddleware.cs +++ b/src/Umbraco.Web/Security/PreviewAuthenticationMiddleware.cs @@ -1,28 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Security.Claims; -using System.Threading; +using System.Security.Claims; using System.Threading.Tasks; using System.Web; -using Microsoft.AspNet.Identity; -using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; -using Microsoft.Owin.Extensions; -using Microsoft.Owin.Logging; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Cookies; -using Owin; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; -using Constants = Umbraco.Core.Constants; -namespace Umbraco.Web.Security.Identity +namespace Umbraco.Web.Security { internal class PreviewAuthenticationMiddleware : OwinMiddleware { diff --git a/src/Umbraco.Core/Security/SessionIdValidator.cs b/src/Umbraco.Web/Security/SessionIdValidator.cs similarity index 97% rename from src/Umbraco.Core/Security/SessionIdValidator.cs rename to src/Umbraco.Web/Security/SessionIdValidator.cs index f738ad9c22..e5e1394aea 100644 --- a/src/Umbraco.Core/Security/SessionIdValidator.cs +++ b/src/Umbraco.Web/Security/SessionIdValidator.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Concurrent; using System.Security.Claims; -using System.Threading; using System.Threading.Tasks; using System.Web; using Microsoft.AspNet.Identity; @@ -9,10 +7,12 @@ using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Microsoft.Owin.Infrastructure; using Microsoft.Owin.Security.Cookies; +using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Models.Identity; +using Umbraco.Core.Security; +using Constants = Umbraco.Core.Constants; -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { /// /// Static helper class used to configure a CookieAuthenticationProvider to validate a cookie against a user's session id diff --git a/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs b/src/Umbraco.Web/Security/UmbracoBackOfficeCookieAuthOptions.cs similarity index 97% rename from src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs rename to src/Umbraco.Web/Security/UmbracoBackOfficeCookieAuthOptions.cs index a4d3852948..b15be9ec9a 100644 --- a/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs +++ b/src/Umbraco.Web/Security/UmbracoBackOfficeCookieAuthOptions.cs @@ -5,9 +5,8 @@ using Microsoft.Owin.Security.Cookies; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Web.Composing; -namespace Umbraco.Web.Security.Identity +namespace Umbraco.Web.Security { /// /// Umbraco auth cookie options diff --git a/src/Umbraco.Web/Security/Identity/UmbracoSecureDataFormat.cs b/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs similarity index 93% rename from src/Umbraco.Web/Security/Identity/UmbracoSecureDataFormat.cs rename to src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs index 2676a5ee25..0dc73f5214 100644 --- a/src/Umbraco.Web/Security/Identity/UmbracoSecureDataFormat.cs +++ b/src/Umbraco.Web/Security/UmbracoSecureDataFormat.cs @@ -1,14 +1,8 @@ using System; -using System.Security.Claims; -using System.Web.Security; -using Microsoft.Owin; using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Cookies; -using Newtonsoft.Json; -using Owin; using Umbraco.Core.Security; -namespace Umbraco.Web.Security.Identity +namespace Umbraco.Web.Security { /// diff --git a/src/Umbraco.Web/Services/SectionService.cs b/src/Umbraco.Web/Services/SectionService.cs index 411d122d49..ff8279a411 100644 --- a/src/Umbraco.Web/Services/SectionService.cs +++ b/src/Umbraco.Web/Services/SectionService.cs @@ -92,7 +92,6 @@ namespace Umbraco.Web.Services doc.Root.Add(new XElement("add", new XAttribute("alias", attr.Alias), new XAttribute("name", attr.Name), - new XAttribute("icon", attr.Icon), new XAttribute("sortOrder", attr.SortOrder))); count++; } @@ -201,7 +200,7 @@ namespace Umbraco.Web.Services }, true); //raise event - OnNew(new Section(name, alias, icon, sortOrder), new EventArgs()); + OnNew(new Section(name, alias, sortOrder), new EventArgs()); } } @@ -255,7 +254,6 @@ namespace Umbraco.Web.Services var sortOrderAttr = addElement.Attribute("sortOrder"); tmp.Add(new Section(addElement.Attribute("name").Value, addElement.Attribute("alias").Value, - addElement.Attribute("icon").Value, sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0)); } return false; @@ -300,7 +298,7 @@ namespace Umbraco.Web.Services //we need to interrogate the attributes for the data. Would be better to have a base class that contains //metadata populated by the attribute. Oh well i guess. var attrs = types.Select(x => x.GetCustomAttributes(false).Single()); - return Enumerable.ToArray
(attrs.Select(x => new Section(x.Name, x.Alias, x.Icon, x.SortOrder))); + return Enumerable.ToArray
(attrs.Select(x => new Section(x.Name, x.Alias, x.SortOrder))); }); } diff --git a/src/Umbraco.Web/Templates/TemplateRenderer.cs b/src/Umbraco.Web/Templates/TemplateRenderer.cs index 44e9cca5a5..ecc1d3bd23 100644 --- a/src/Umbraco.Web/Templates/TemplateRenderer.cs +++ b/src/Umbraco.Web/Templates/TemplateRenderer.cs @@ -202,7 +202,6 @@ namespace Umbraco.Web.Templates // handlers like default.aspx will want it and most macros currently need it request.UmbracoPage = new page(request); //now, set the new ones for this page execution - _umbracoContext.HttpContext.Items["pageID"] = request.PublishedContent.Id; _umbracoContext.HttpContext.Items["pageElements"] = request.UmbracoPage.Elements; _umbracoContext.HttpContext.Items[Core.Constants.Conventions.Url.AltTemplate] = null; _umbracoContext.PublishedRequest = request; @@ -214,8 +213,8 @@ namespace Umbraco.Web.Templates private void SaveExistingItems() { //Many objects require that these legacy items are in the http context items... before we render this template we need to first - //save the values in them so that we can re-set them after we render so the rest of the execution works as per normal. - _oldPageId = _umbracoContext.HttpContext.Items["pageID"]; + //save the values in them so that we can re-set them after we render so the rest of the execution works as per normal + _oldPageId = _umbracoContext.PageId; _oldPageElements = _umbracoContext.HttpContext.Items["pageElements"]; _oldPublishedRequest = _umbracoContext.PublishedRequest; _oldAltTemplate = _umbracoContext.HttpContext.Items[Umbraco.Core.Constants.Conventions.Url.AltTemplate]; @@ -227,7 +226,6 @@ namespace Umbraco.Web.Templates private void RestoreItems() { _umbracoContext.PublishedRequest = _oldPublishedRequest; - _umbracoContext.HttpContext.Items["pageID"] = _oldPageId; _umbracoContext.HttpContext.Items["pageElements"] = _oldPageElements; _umbracoContext.HttpContext.Items[Umbraco.Core.Constants.Conventions.Url.AltTemplate] = _oldAltTemplate; } diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 782878b5c0..9c13429b44 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -1,9 +1,10 @@ -using System; +using HtmlAgilityPack; +using System; +using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; -using Umbraco.Core.Logging; using Umbraco.Web.Composing; using Umbraco.Web.Routing; @@ -17,13 +18,6 @@ namespace Umbraco.Web.Templates ///
public static class TemplateUtilities { - //TODO: Pass in an Umbraco context!!!!!!!! Don't rely on the singleton so things are more testable - [Obsolete("Use the overload specifying an UmbracoContext")] - internal static string ParseInternalLinks(string text, bool preview) - { - return ParseInternalLinks(text, preview, UmbracoContext.Current); - } - internal static string ParseInternalLinks(string text, bool preview, UmbracoContext umbracoContext) { using (umbracoContext.ForcedPreview(preview)) // force for url provider @@ -44,6 +38,11 @@ namespace Umbraco.Web.Templates { if (urlProvider == null) throw new ArgumentNullException("urlProvider"); + if(string.IsNullOrEmpty(text)) + { + return text; + } + // Parse internal links var tags = LocalLinkPattern.Matches(text); foreach (Match tag in tags) @@ -72,6 +71,11 @@ namespace Umbraco.Web.Templates } } + if (UmbracoConfig.For.UmbracoSettings().Content.StripUdiAttributes) + { + text = StripUdiDataAttributes(text); + } + return text; } @@ -83,6 +87,9 @@ namespace Umbraco.Web.Templates private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + private static readonly Regex UdiDataAttributePattern = new Regex("data-udi=\"[^\\\"]*\"", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + /// /// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path. /// @@ -100,7 +107,7 @@ namespace Umbraco.Web.Templates { // find all relative urls (ie. urls that contain ~) var tags = ResolveUrlPattern.Matches(text); - Current.Logger.Debug(typeof(IOHelper), "After regex: {ElapsedMilliseconds} matched: {TagsCount}", timer.Stopwatch.ElapsedMilliseconds, tags.Count); + Current.Logger.Debug(typeof(IOHelper), "After regex: {Duration} matched: {TagsCount}", timer.Stopwatch.ElapsedMilliseconds, tags.Count); foreach (Match tag in tags) { var url = ""; @@ -126,5 +133,21 @@ namespace Umbraco.Web.Templates { return text.CleanForXss(ignoreFromClean); } + + /// + /// Strips data-udi attributes from rich text + /// + /// A html string + /// A string stripped from the data-uid attributes + public static string StripUdiDataAttributes(string input) + { + if (string.IsNullOrEmpty(input)) + { + return string.Empty; + } + + + return UdiDataAttributePattern.Replace(input, string.Empty); + } } } diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 9403847df9..d489421353 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Http.Formatting; using System.Threading.Tasks; using System.Web.Http; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; @@ -30,6 +31,8 @@ namespace Umbraco.Web.Trees [HttpQueryStringFilter("queryStrings")] public async Task GetApplicationTrees(string application, string tree, FormDataCollection queryStrings, bool onlyInitialized = true) { + application = application.CleanForXss(); + if (string.IsNullOrEmpty(application)) throw new HttpResponseException(HttpStatusCode.NotFound); var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture); @@ -37,7 +40,7 @@ namespace Umbraco.Web.Trees //find all tree definitions that have the current application alias var appTrees = Services.ApplicationTreeService.GetApplicationTrees(application, onlyInitialized).ToArray(); - if (appTrees.Length == 1 || string.IsNullOrEmpty(tree) == false ) + if (string.IsNullOrEmpty(tree) == false || appTrees.Length <= 1) { var apptree = string.IsNullOrEmpty(tree) == false ? appTrees.SingleOrDefault(x => x.Alias == tree) diff --git a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs index 145a0f5947..5cb63d7409 100644 --- a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.Trees /// This authorizes based on access to the content section even though it exists in the settings /// [UmbracoApplicationAuthorize(Constants.Applications.Content)] - [Tree(Constants.Applications.Settings, Constants.Trees.ContentBlueprints, null, sortOrder: 10)] + [Tree(Constants.Applications.Settings, Constants.Trees.ContentBlueprints, null, sortOrder: 12)] [PluginController("UmbracoTrees")] [CoreTree] public class ContentBlueprintTreeController : TreeController @@ -30,7 +30,7 @@ namespace Umbraco.Web.Trees var root = base.CreateRootNode(queryStrings); //this will load in a custom UI instead of the dashboard for the root node - root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Settings, Constants.Trees.ContentBlueprints, "intro"); + root.RoutePath = $"{Constants.Applications.Settings}/{Constants.Trees.ContentBlueprints}/intro"; return root; } @@ -58,7 +58,7 @@ namespace Umbraco.Web.Trees .Select(entity => { var treeNode = CreateTreeNode(entity, Constants.ObjectTypes.DocumentBlueprint, id, queryStrings, "icon-item-arrangement", true); - treeNode.Path = string.Format("-1,{0}", entity.Id); + treeNode.Path = $"-1,{entity.Id}"; treeNode.NodeType = "document-type-blueprints"; //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; @@ -92,8 +92,9 @@ namespace Umbraco.Web.Trees if (id == Constants.System.Root.ToInvariantString()) { // root actions - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); + menu.Items.Add(Services.TextService.Localize($"actions/{ActionNew.Instance.Alias}")); + menu.Items.Add(Services.TextService.Localize( + $"actions/{ActionRefresh.Instance.Alias}"), true); return menu; } var cte = Services.EntityService.Get(int.Parse(id), UmbracoObjectTypes.DocumentType); diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 33a347d3d4..3a990a7741 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.Trees Constants.Applications.Media, Constants.Applications.Users, Constants.Applications.Settings, - Constants.Applications.Developer, + Constants.Applications.Packages, Constants.Applications.Members)] [Tree(Constants.Applications.Content, Constants.Trees.Content)] [PluginController("UmbracoTrees")] @@ -47,10 +47,10 @@ namespace Umbraco.Web.Trees /// protected override TreeNode GetSingleTreeNode(IEntitySlim entity, string parentId, FormDataCollection queryStrings) { - var langId = queryStrings?["culture"]; + var culture = queryStrings?["culture"]; var allowedUserOptions = GetAllowedUserMenuItemsForNode(entity); - if (CanUserAccessNode(entity, allowedUserOptions, langId)) + if (CanUserAccessNode(entity, allowedUserOptions, culture)) { //Special check to see if it ia a container, if so then we'll hide children. var isContainer = entity.IsContainer; // && (queryStrings.Get("isDialog") != "true"); @@ -74,11 +74,23 @@ namespace Umbraco.Web.Trees { var documentEntity = (IDocumentEntitySlim) entity; - //fixme we need these statuses per variant but to do that we need to fix the issues listed in IDocumentEntitySlim - if (!documentEntity.Published) - node.SetNotPublishedStyle(); - //if (documentEntity.Edited) - // node.SetHasUnpublishedVersionStyle(); + if (!documentEntity.Variations.VariesByCulture()) + { + if (!documentEntity.Published) + node.SetNotPublishedStyle(); + else if (documentEntity.Edited) + node.SetHasPendingVersionStyle(); + } + else + { + if (!culture.IsNullOrWhiteSpace()) + { + if (!documentEntity.PublishedCultures.Contains(culture)) + node.SetNotPublishedStyle(); + else if (documentEntity.EditedCultures.Contains(culture)) + node.SetHasPendingVersionStyle(); + } + } node.AdditionalData.Add("contentType", documentEntity.ContentTypeAlias); } @@ -223,11 +235,10 @@ namespace Umbraco.Web.Trees //need to ensure some of these are converted to the legacy system - until we upgrade them all to be angularized. AddActionNode(item, menu, true); AddActionNode(item, menu); - AddActionNode(item, menu, convert: true); AddActionNode(item, menu, true); - AddActionNode(item, menu, convert: true); + AddActionNode(item, menu); AddActionNode(item, menu, convert: true); AddActionNode(item, menu); AddActionNode(item, menu, convert: true); diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index da1eae7b2c..2ba11997e6 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -20,6 +20,14 @@ namespace Umbraco.Web.Trees [CoreTree] public class ContentTypeTreeController : TreeController, ISearchableTree { + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + //check if there are any types + root.HasChildren = Services.ContentTypeService.GetAll().Any(); + return root; + } + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { var intId = id.TryConvertTo(); diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 99b94b544c..a2201f792a 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -17,7 +17,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.DataTypes)] - [Tree(Constants.Applications.Settings, Constants.Trees.DataTypes, null, sortOrder:7)] + [Tree(Constants.Applications.Settings, Constants.Trees.DataTypes, null, sortOrder:3)] [PluginController("UmbracoTrees")] [CoreTree] public class DataTypeTreeController : TreeController, ISearchableTree @@ -46,8 +46,8 @@ namespace Umbraco.Web.Trees //if the request is for folders only then just return if (queryStrings["foldersonly"].IsNullOrWhiteSpace() == false && queryStrings["foldersonly"] == "1") return nodes; - //Normal nodes - var sysIds = GetSystemIds(); + //System ListView nodes + var systemListViewDataTypeIds = GetNonDeletableSystemListViewDataTypeIds(); nodes.AddRange( Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.DataType) @@ -56,7 +56,7 @@ namespace Umbraco.Web.Trees { var node = CreateTreeNode(dt.Id.ToInvariantString(), id, queryStrings, dt.Name, "icon-autofill", false); node.Path = dt.Path; - if (sysIds.Contains(dt.Id)) + if (systemListViewDataTypeIds.Contains(dt.Id)) { node.Icon = "icon-thumbnail-list"; } @@ -66,15 +66,31 @@ namespace Umbraco.Web.Trees return nodes; } - private IEnumerable GetSystemIds() + /// + /// Get all integer identifiers for the non-deletable system datatypes. + /// + private static IEnumerable GetNonDeletableSystemDataTypeIds() { var systemIds = new[] + { + Constants.System.DefaultLabelDataTypeId + }; + + return systemIds.Concat(GetNonDeletableSystemListViewDataTypeIds()); + } + + /// + /// Get all integer identifiers for the non-deletable system listviews. + /// + private static IEnumerable GetNonDeletableSystemListViewDataTypeIds() + { + return new[] { Constants.DataTypes.DefaultContentListView, Constants.DataTypes.DefaultMediaListView, Constants.DataTypes.DefaultMembersListView + }; - return systemIds; } protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) @@ -87,7 +103,7 @@ namespace Umbraco.Web.Trees menu.DefaultMenuAlias = ActionNew.Instance.Alias; // root actions - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize($"actions/{ActionNew.Instance.Alias}")); menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); return menu; } @@ -98,30 +114,29 @@ namespace Umbraco.Web.Trees //set the default to create menu.DefaultMenuAlias = ActionNew.Instance.Alias; - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize($"actions/{ActionNew.Instance.Alias}")); - menu.Items.Add(new MenuItem("rename", Services.TextService.Localize(String.Format("actions/{0}", "rename"))) + menu.Items.Add(new MenuItem("rename", Services.TextService.Localize("actions/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($"actions/{ActionDelete.Instance.Alias}")); } - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), hasSeparator: true); + menu.Items.Add(Services.TextService.Localize( + $"actions/{ActionRefresh.Instance.Alias}"), hasSeparator: true); } else { - var sysIds = GetSystemIds(); + var nonDeletableSystemDataTypeIds = GetNonDeletableSystemDataTypeIds(); - if (sysIds.Contains(int.Parse(id)) == false) - { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); - } + if (nonDeletableSystemDataTypeIds.Contains(int.Parse(id)) == false) + menu.Items.Add(Services.TextService.Localize($"actions/{ActionDelete.Instance.Alias}")); - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), hasSeparator: true); + menu.Items.Add(Services.TextService.Localize($"actions/{ActionMove.Instance.Alias}"), hasSeparator: true); } return menu; diff --git a/src/Umbraco.Web/Trees/DictionaryTreeController.cs b/src/Umbraco.Web/Trees/DictionaryTreeController.cs index c2491fefe0..27039832c9 100644 --- a/src/Umbraco.Web/Trees/DictionaryTreeController.cs +++ b/src/Umbraco.Web/Trees/DictionaryTreeController.cs @@ -9,10 +9,11 @@ using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees { + [UmbracoTreeAuthorize(Constants.Trees.Dictionary)] [Mvc.PluginController("UmbracoTrees")] [CoreTree] - [Tree(Constants.Applications.Settings, Constants.Trees.Dictionary, null, sortOrder: 3)] + [Tree(Constants.Applications.Translation, Constants.Trees.Dictionary, null, sortOrder: 0)] public class DictionaryTreeController : TreeController { protected override TreeNode CreateRootNode(FormDataCollection queryStrings) @@ -21,7 +22,7 @@ namespace Umbraco.Web.Trees // the default section is settings, falling back to this if we can't // figure out where we are from the querystring parameters - var section = Constants.Applications.Settings; + var section = Constants.Applications.Translation; if (queryStrings["application"] != null) section = queryStrings["application"]; diff --git a/src/Umbraco.Web/Trees/LanguageTreeController.cs b/src/Umbraco.Web/Trees/LanguageTreeController.cs index eadb5c50d0..b65906d152 100644 --- a/src/Umbraco.Web/Trees/LanguageTreeController.cs +++ b/src/Umbraco.Web/Trees/LanguageTreeController.cs @@ -7,7 +7,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Languages)] - [Tree(Constants.Applications.Settings, Constants.Trees.Languages, null, sortOrder: 5)] + [Tree(Constants.Applications.Settings, Constants.Trees.Languages, null, sortOrder: 11)] [PluginController("UmbracoTrees")] [CoreTree] public class LanguageTreeController : TreeController @@ -33,7 +33,7 @@ namespace Umbraco.Web.Trees var root = base.CreateRootNode(queryStrings); //this will load in a custom UI instead of the dashboard for the root node - root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Settings, Constants.Trees.Languages, "overview"); + root.RoutePath = $"{Constants.Applications.Settings}/{Constants.Trees.Languages}/overview"; root.Icon = "icon-globe"; root.HasChildren = false; root.MenuUrl = null; diff --git a/src/Umbraco.Web/Trees/MacrosTreeController.cs b/src/Umbraco.Web/Trees/MacrosTreeController.cs index 2968408bdb..66f92ffdc0 100644 --- a/src/Umbraco.Web/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/MacrosTreeController.cs @@ -13,10 +13,11 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Macros)] - [Tree(Constants.Applications.Developer, Constants.Trees.Macros, "Macros", sortOrder: 2)] + [Tree(Constants.Applications.Settings, Constants.Trees.Macros, "Macros", sortOrder: 4)] [PluginController("UmbracoTrees")] [CoreTree] - public class MacrosTreeController : TreeController + public class + MacrosTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index 53f1b0a97e..8eb37e6224 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Trees Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Settings, - Constants.Applications.Developer, + Constants.Applications.Packages, Constants.Applications.Members)] [Tree(Constants.Applications.Media, Constants.Trees.Media)] [PluginController("UmbracoTrees")] diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index 086c1a5194..55a15b683c 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -16,7 +16,7 @@ using Umbraco.Web.Search; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] - [Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, null, sortOrder:9)] + [Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, null, sortOrder:1)] [Mvc.PluginController("UmbracoTrees")] [CoreTree] public class MediaTypeTreeController : TreeController, ISearchableTree @@ -73,8 +73,9 @@ namespace Umbraco.Web.Trees menu.DefaultMenuAlias = ActionNew.Instance.Alias; // root actions - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize($"actions/{ActionNew.Instance.Alias}")); + menu.Items.Add(Services.TextService.Localize( + $"actions/{ActionRefresh.Instance.Alias}")); return menu; } @@ -84,9 +85,9 @@ namespace Umbraco.Web.Trees //set the default to create menu.DefaultMenuAlias = ActionNew.Instance.Alias; - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize($"actions/{ActionNew.Instance.Alias}")); - menu.Items.Add(new MenuItem("rename", Services.TextService.Localize(String.Format("actions/{0}", "rename"))) + menu.Items.Add(new MenuItem("rename", Services.TextService.Localize("actions/rename")) { Icon = "icon icon-edit" }); @@ -94,9 +95,10 @@ namespace Umbraco.Web.Trees 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($"actions/{ActionDelete.Instance.Alias}")); } - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), hasSeparator: true); + menu.Items.Add(Services.TextService.Localize( + $"actions/{ActionRefresh.Instance.Alias}"), hasSeparator: true); } else { @@ -105,28 +107,29 @@ namespace Umbraco.Web.Trees if (enableInheritedMediaTypes) { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize($"actions/{ActionNew.Instance.Alias}")); //no move action if this is a child doc type if (parent == null) { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true); + menu.Items.Add(Services.TextService.Localize($"actions/{ActionMove.Instance.Alias}"), true); } } else { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize($"actions/{ActionMove.Instance.Alias}")); //no move action if this is a child doc type if (parent == null) { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true); + menu.Items.Add(Services.TextService.Localize($"actions/{ActionMove.Instance.Alias}"), true); } } - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionCopy.Instance.Alias))); - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize($"actions/{ActionCopy.Instance.Alias}")); + menu.Items.Add(Services.TextService.Localize($"actions/{ActionDelete.Instance.Alias}")); if (enableInheritedMediaTypes) - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); + menu.Items.Add(Services.TextService.Localize( + $"actions/{ActionRefresh.Instance.Alias}"), true); } return menu; diff --git a/src/Umbraco.Web/Trees/MemberGroupTreeController.cs b/src/Umbraco.Web/Trees/MemberGroupTreeController.cs index d1a0d2c318..b9910c7b31 100644 --- a/src/Umbraco.Web/Trees/MemberGroupTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberGroupTreeController.cs @@ -8,7 +8,7 @@ using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.MemberGroups)] - [Tree(Constants.Applications.Members, Constants.Trees.MemberGroups, null, sortOrder: 2)] + [Tree(Constants.Applications.Members, Constants.Trees.MemberGroups, null, sortOrder: 1)] [Mvc.PluginController("UmbracoTrees")] [CoreTree] public class MemberGroupTreeController : MemberTypeAndGroupTreeControllerBase diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs index 10b5d35b6e..56b836ce8a 100644 --- a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -8,7 +8,7 @@ using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] - [Tree(Constants.Applications.Settings, Constants.Trees.MemberTypes, null, sortOrder: 1)] + [Tree(Constants.Applications.Settings, Constants.Trees.MemberTypes, null, sortOrder: 2)] public class MemberTypeTreeController : MemberTypeAndGroupTreeControllerBase { protected override IEnumerable GetTreeNodesFromService(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/PackagesTreeController.cs b/src/Umbraco.Web/Trees/PackagesTreeController.cs index 31283acebf..31f577ad85 100644 --- a/src/Umbraco.Web/Trees/PackagesTreeController.cs +++ b/src/Umbraco.Web/Trees/PackagesTreeController.cs @@ -1,107 +1,106 @@ -//using System; -//using System.Linq; -//using System.Net.Http.Formatting; -//using Umbraco.Web.Models.Trees; -//using Umbraco.Web.Mvc; -//using Umbraco.Web.WebApi.Filters; -//using umbraco; -//using umbraco.cms.businesslogic.packager; -//using Umbraco.Core.Services; -//using Umbraco.Web._Legacy.Actions; -//using Constants = Umbraco.Core.Constants; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Formatting; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using umbraco; +using umbraco.cms.businesslogic.packager; +using Umbraco.Core.Services; +using Umbraco.Web._Legacy.Actions; +using Constants = Umbraco.Core.Constants; -//namespace Umbraco.Web.Trees -//{ -// [UmbracoTreeAuthorize(Constants.Trees.Packages)] -// [Tree(Constants.Applications.Developer, Constants.Trees.Packages, null, sortOrder: 0)] -// [PluginController("UmbracoTrees")] -// [CoreTree] -// public class PackagesTreeController : TreeController -// { -// /// -// /// Helper method to create a root model for a tree -// /// -// /// -// protected override TreeNode CreateRootNode(FormDataCollection queryStrings) -// { -// var root = base.CreateRootNode(queryStrings); +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.Packages)] + [Tree(Constants.Applications.Packages, Constants.Trees.Packages, null, sortOrder: 0)] + [PluginController("UmbracoTrees")] + [CoreTree] + public class PackagesTreeController : TreeController + { + /// + /// Helper method to create a root model for a tree + /// + /// + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + + root.RoutePath = $"{Constants.Applications.Packages}/{Constants.Trees.Packages}/overview"; + + root.Icon = "icon-box"; + + return root; + } + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); -// //this will load in a custom UI instead of the dashboard for the root node -// root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Developer, Constants.Trees.Packages, "overview"); -// root.Icon = "icon-box"; + var createdPackages = CreatedPackage.GetAllCreatedPackages(); -// return root; -// } -// protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) -// { -// var nodes = new TreeNodeCollection(); - -// var createdPackages = CreatedPackage.GetAllCreatedPackages(); - -// if (id == "created") -// { -// nodes.AddRange( -// createdPackages -// .OrderBy(entity => entity.Data.Name) -// .Select(dt => -// { -// var node = CreateTreeNode(dt.Data.Id.ToString(), id, queryStrings, dt.Data.Name, "icon-inbox", false, -// string.Format("/{0}/framed/{1}", -// queryStrings.GetValue("application"), -// Uri.EscapeDataString("developer/Packages/EditPackage.aspx?id=" + dt.Data.Id))); -// return node; -// })); -// } -// else -// { -// //must be root -// var node = CreateTreeNode( -// "created", -// id, -// queryStrings, -// Services.TextService.Localize("treeHeaders/createdPackages"), -// "icon-folder", -// createdPackages.Count > 0, -// string.Empty); + if (id == "created") + { + nodes.AddRange( + createdPackages + .OrderBy(entity => entity.Data.Name) + .Select(dt => + { + var node = CreateTreeNode(dt.Data.Id.ToString(), id, queryStrings, dt.Data.Name, "icon-inbox", false, + $"/{queryStrings.GetValue("application")}/framed/{Uri.EscapeDataString("developer/Packages/EditPackage.aspx?id=" + dt.Data.Id)}"); + return node; + })); + } + else + { + //must be root + var node = CreateTreeNode( + "created", + id, + queryStrings, + Services.TextService.Localize("treeHeaders/createdPackages"), + "icon-folder", + createdPackages.Count > 0, + string.Empty); -// //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. -// node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; -// nodes.Add(node); -// } + nodes.Add(node); + } -// return nodes; -// } + return nodes; + } -// protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) -// { -// var menu = new MenuItemCollection(); + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); -// // Root actions -// if (id == "-1") -// { -// menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))) -// .ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue("application")); -// } -// else if (id == "created") -// { -// menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))) -// .ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue("application")); + // Root actions + if (id == "-1") + { + menu.Items.Add(Services.TextService.Localize($"actions/{ActionNew.Instance.Alias}")) + .ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue("application")); + } + else if (id == "created") + { + menu.Items.Add(Services.TextService.Localize($"actions/{ActionNew.Instance.Alias}")) + .ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue("application")); -// menu.Items.Add( -// Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); -// } -// else -// { -// //it's a package node -// menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)); -// } + menu.Items.Add( + Services.TextService.Localize($"actions/{ActionRefresh.Instance.Alias}"), true); + } + else + { + //it's a package node + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)); + } -// return menu; -// } -// } -//} + return menu; + } + } +} diff --git a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs index db97d5c3a4..882cfb2c9f 100644 --- a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs @@ -10,7 +10,7 @@ namespace Umbraco.Web.Trees /// /// Tree for displaying partial view macros in the developer app /// - [Tree(Constants.Applications.Developer, Constants.Trees.PartialViewMacros, null, sortOrder: 6)] + [Tree(Constants.Applications.Settings, Constants.Trees.PartialViewMacros, null, sortOrder: 8)] [UmbracoTreeAuthorize(Constants.Trees.PartialViewMacros)] [PluginController("UmbracoTrees")] [CoreTree] diff --git a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs index aaaeb2d175..41c53fdc99 100644 --- a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Trees /// /// Tree for displaying partial views in the settings app /// - [Tree(Constants.Applications.Settings, Constants.Trees.PartialViews, null, sortOrder: 2)] + [Tree(Constants.Applications.Settings, Constants.Trees.PartialViews, null, sortOrder: 7)] [UmbracoTreeAuthorize(Constants.Trees.PartialViews)] [PluginController("UmbracoTrees")] [CoreTree] diff --git a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs index 14fd817a7c..e35a9a23b6 100644 --- a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs @@ -12,7 +12,7 @@ using Umbraco.Core.Models.Entities; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.RelationTypes)] - [Tree(Constants.Applications.Developer, Constants.Trees.RelationTypes, null, sortOrder: 4)] + [Tree(Constants.Applications.Settings, Constants.Trees.RelationTypes, null, sortOrder: 5)] [Mvc.PluginController("UmbracoTrees")] [CoreTree] public class RelationTypeTreeController : TreeController diff --git a/src/Umbraco.Web/Trees/ScriptsTreeController.cs b/src/Umbraco.Web/Trees/ScriptsTreeController.cs index 57a50cde5d..97053993b4 100644 --- a/src/Umbraco.Web/Trees/ScriptsTreeController.cs +++ b/src/Umbraco.Web/Trees/ScriptsTreeController.cs @@ -6,7 +6,7 @@ using Umbraco.Web.Models.Trees; namespace Umbraco.Web.Trees { - [Tree(Constants.Applications.Settings, Constants.Trees.Scripts, "Scripts", "icon-folder", "icon-folder", sortOrder: 4)] + [Tree(Constants.Applications.Settings, Constants.Trees.Scripts, "Scripts", "icon-folder", "icon-folder", sortOrder: 10)] public class ScriptsTreeController : FileSystemTreeController { protected override IFileSystem FileSystem => Current.FileSystems.ScriptsFileSystem; // fixme inject diff --git a/src/Umbraco.Web/Trees/StylesheetsTreeController.cs b/src/Umbraco.Web/Trees/StylesheetsTreeController.cs index 650407296e..365f427e18 100644 --- a/src/Umbraco.Web/Trees/StylesheetsTreeController.cs +++ b/src/Umbraco.Web/Trees/StylesheetsTreeController.cs @@ -4,7 +4,7 @@ using Umbraco.Web.Composing; namespace Umbraco.Web.Trees { - [Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, "Stylesheets", "icon-folder", "icon-folder", sortOrder: 3)] + [Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, "Stylesheets", "icon-folder", "icon-folder", sortOrder: 9)] public class StylesheetsTreeController : FileSystemTreeController { protected override IFileSystem FileSystem => Current.FileSystems.StylesheetsFileSystem; // fixme inject diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index d9aa0f21a0..521475413c 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -19,7 +19,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Templates)] - [Tree(Constants.Applications.Settings, Constants.Trees.Templates, null, sortOrder:1)] + [Tree(Constants.Applications.Settings, Constants.Trees.Templates, null, sortOrder:6)] [PluginController("UmbracoTrees")] [CoreTree] public class TemplatesTreeController : TreeController, ISearchableTree @@ -70,7 +70,7 @@ namespace Umbraco.Web.Trees //Create the normal create action var item = menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)); - item.NavigateToRoute(string.Format("{0}/templates/edit/{1}?create=true", queryStrings.GetValue("application"), id)); + item.NavigateToRoute($"{queryStrings.GetValue("application")}/templates/edit/{id}?create=true"); if (id == Constants.System.Root.ToInvariantString()) { diff --git a/src/Umbraco.Web/UI/Controls/ProgressBar.cs b/src/Umbraco.Web/UI/Controls/ProgressBar.cs index 8f4a12c9be..0c97e96644 100644 --- a/src/Umbraco.Web/UI/Controls/ProgressBar.cs +++ b/src/Umbraco.Web/UI/Controls/ProgressBar.cs @@ -8,6 +8,7 @@ namespace Umbraco.Web.UI.Controls protected override void Render(System.Web.UI.HtmlTextWriter writer) { + // fixme - image is gone! base.ImageUrl = SystemDirectories.UmbracoClient + "/images/progressBar.gif"; base.AlternateText = Title; diff --git a/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs b/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs index 9c5e0f1ec2..cd7270ac62 100644 --- a/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs +++ b/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.UI.JavaScript return toParse.Split(new[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries); } - protected IEnumerable OptimizeAssetCollection(IEnumerable assets, ClientDependencyType assetType, HttpContextBase httpContext) + internal static IEnumerable OptimizeAssetCollection(IEnumerable assets, ClientDependencyType assetType, HttpContextBase httpContext) { if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); @@ -41,11 +41,11 @@ namespace Umbraco.Web.UI.JavaScript // ike lib/blah/blah.js so we need to turn them into absolutes here if (x.StartsWith("/") == false && Uri.IsWellFormedUriString(x, UriKind.Relative)) { - return (IClientDependencyFile) new BasicFile(assetType) { FilePath = new Uri(requestUrl, x).AbsolutePath }; + return new BasicFile(assetType) { FilePath = new Uri(requestUrl, x).AbsolutePath }; } return assetType == ClientDependencyType.Javascript - ? (IClientDependencyFile) new JavascriptFile(x) + ? new JavascriptFile(x) : (IClientDependencyFile) new CssFile(x); }).ToList(); diff --git a/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs b/src/Umbraco.Web/UI/JavaScript/ClientDependencyConfiguration.cs similarity index 79% rename from src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs rename to src/Umbraco.Web/UI/JavaScript/ClientDependencyConfiguration.cs index 61f9023167..f1f57a03a7 100644 --- a/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs +++ b/src/Umbraco.Web/UI/JavaScript/ClientDependencyConfiguration.cs @@ -12,7 +12,7 @@ using Semver; using Umbraco.Core.IO; using Umbraco.Core.Logging; -namespace Umbraco.Core.Configuration +namespace Umbraco.Web.UI.JavaScript { /// /// A utility class for working with CDF config and cache files - use sparingly! @@ -91,40 +91,6 @@ namespace Umbraco.Core.Configuration return false; } - /// - /// Changes the version number in ClientDependency.config to a random value to avoid stale caches - /// - /// - [Obsolete("Use the UpdateVersionNumber method specifying the version, date and dateFormat instead")] - public bool IncreaseVersionNumber() - { - try - { - var clientDependencyConfigXml = XDocument.Load(_fileName, LoadOptions.PreserveWhitespace); - if (clientDependencyConfigXml.Root != null) - { - - var versionAttribute = clientDependencyConfigXml.Root.Attribute("version"); - - //Set the new version to the hashcode of now - var oldVersion = versionAttribute.Value; - var newVersion = Math.Abs(DateTime.UtcNow.GetHashCode()); - - versionAttribute.SetValue(newVersion); - clientDependencyConfigXml.Save(_fileName, SaveOptions.DisableFormatting); - - _logger.Info("Updated version number from {OldVersion} to {NewVersion}", oldVersion, newVersion); - return true; - } - } - catch (Exception ex) - { - _logger.Error(ex, "Couldn't update ClientDependency version number"); - } - - return false; - } - /// /// Clears the temporary files stored for the ClientDependency folder /// @@ -141,7 +107,7 @@ namespace Umbraco.Core.Configuration try { - var fullPath = currentHttpContext.Server.MapPath(XmlFileMapper.FileMapVirtualFolder); + var fullPath = currentHttpContext.Server.MapPath(XmlFileMapper.FileMapDefaultFolder); if (fullPath != null) { cdfTempDirectories.Add(fullPath); diff --git a/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs b/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs index 4b3ef62b58..0471b47e8e 100644 --- a/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs +++ b/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs @@ -33,16 +33,20 @@ namespace Umbraco.Web.UI.JavaScript private static readonly Regex Token = new Regex("(\"##\\w+?##\")", RegexOptions.Compiled); /// - /// Processes all found manifest files and outputs the main.js file containing all plugin manifests + /// Gets the JS initialization script to boot the back office application /// - public string GetJavascriptInitialization(HttpContextBase httpContext, IEnumerable umbracoInit, IEnumerable additionalJsFiles = null) + /// + /// + /// + /// The angular module name to boot + /// + /// + public static string GetJavascriptInitialization(HttpContextBase httpContext, IEnumerable scripts, string angularModule) { - var files = GetScriptFiles(httpContext, umbracoInit, additionalJsFiles); - var jarray = new StringBuilder(); jarray.AppendLine("["); var first = true; - foreach (var file in files) + foreach (var file in scripts) { if (first) first = false; else jarray.AppendLine(","); @@ -53,10 +57,22 @@ namespace Umbraco.Web.UI.JavaScript } jarray.Append("]"); - return WriteScript(jarray.ToString(), IOHelper.ResolveUrl(SystemDirectories.Umbraco)); + return WriteScript(jarray.ToString(), IOHelper.ResolveUrl(SystemDirectories.Umbraco), angularModule); } - public IEnumerable GetScriptFiles(HttpContextBase httpContext, IEnumerable umbracoInit, IEnumerable additionalJsFiles = null) + /// + /// Returns a list of optimized script paths for the back office + /// + /// + /// + /// + /// + /// Cache busted/optimized script paths for the back office including manifest and property editor scripts + /// + /// + /// Used to cache bust and optimize script paths for the back office + /// + public IEnumerable OptimizeBackOfficeScriptFiles(HttpContextBase httpContext, IEnumerable umbracoInit, IEnumerable additionalJsFiles = null) { var scripts = new HashSet(); foreach (var script in umbracoInit) @@ -75,6 +91,26 @@ namespace Umbraco.Web.UI.JavaScript return scripts.ToArray(); } + /// + /// Returns a list of optimized script paths + /// + /// + /// + /// + /// + /// Used to cache bust and optimize script paths + /// + public static IEnumerable OptimizeScriptFiles(HttpContextBase httpContext, IEnumerable scriptFiles) + { + var scripts = new HashSet(); + foreach (var script in scriptFiles) + scripts.Add(script); + + scripts = new HashSet(OptimizeAssetCollection(scripts, ClientDependencyType.Javascript, httpContext)); + + return scripts.ToArray(); + } + /// /// Returns the default config as a JArray /// @@ -85,15 +121,25 @@ namespace Umbraco.Web.UI.JavaScript return resources.Where(x => x.Type == JTokenType.String).Select(x => x.ToString()); } + /// + /// Returns the default config as a JArray + /// + /// + internal static IEnumerable GetPreviewInitialization() + { + var resources = JsonConvert.DeserializeObject(Resources.PreviewInitialize); + return resources.Where(x => x.Type == JTokenType.String).Select(x => x.ToString()); + } + /// /// Parses the JsResources.Main and replaces the replacement tokens accordingly. /// /// /// - internal static string WriteScript(params string[] replacements) + internal static string WriteScript(string scripts, string umbracoPath, string angularModule) { var count = 0; - + var replacements = new[] { scripts, umbracoPath, angularModule }; // replace, catering for the special syntax when we have // js function() objects contained in the json diff --git a/src/Umbraco.Web/UI/JavaScript/Main.js b/src/Umbraco.Web/UI/JavaScript/Main.js index 2998a98b6a..8aa431376a 100644 --- a/src/Umbraco.Web/UI/JavaScript/Main.js +++ b/src/Umbraco.Web/UI/JavaScript/Main.js @@ -1,10 +1,12 @@ LazyLoad.js("##JsInitialize##", function () { //we need to set the legacy UmbClientMgr path - UmbClientMgr.setUmbracoPath('"##UmbracoPath##"'); + if ((typeof UmbClientMgr) !== "undefined") { + UmbClientMgr.setUmbracoPath('"##UmbracoPath##"'); + } jQuery(document).ready(function () { - angular.bootstrap(document, ['umbraco']); + angular.bootstrap(document, ['"##AngularModule##"']); }); -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web/UI/JavaScript/PreviewInitialize.js b/src/Umbraco.Web/UI/JavaScript/PreviewInitialize.js new file mode 100644 index 0000000000..0b0c24f378 --- /dev/null +++ b/src/Umbraco.Web/UI/JavaScript/PreviewInitialize.js @@ -0,0 +1,14 @@ +[ + '../lib/jquery/jquery.min.js', + '../lib/angular/angular.js', + '../lib/underscore/underscore-min.js', + '../lib/umbraco/Extensions.js', + '../js/app.js', + '../js/umbraco.resources.js', + '../js/umbraco.services.js', + '../js/umbraco.interceptors.js', + '../ServerVariables', + '../lib/signalr/jquery.signalR.js', + '../BackOffice/signalr/hubs', + '../js/umbraco.preview.js' +] diff --git a/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs b/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs index 4ac359c360..2f320ef839 100644 --- a/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs +++ b/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs @@ -63,22 +63,22 @@ namespace Umbraco.Web.UI.JavaScript { /// /// Looks up a localized string similar to [ /// 'lib/jquery/jquery.min.js', - /// 'lib/angular/1.1.5/angular.min.js', + /// 'lib/jquery-ui/jquery-ui.min.js', + /// 'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js', + /// + /// 'lib/angular/angular.js', /// 'lib/underscore/underscore-min.js', /// /// 'lib/moment/moment.min.js', /// - /// 'lib/jquery-ui/jquery-ui.min.js', - /// 'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js', + /// 'lib/animejs/anime.min.js', /// - /// 'lib/angular/1.1.5/angular-cookies.min.js', - /// 'lib/angular/1.1.5/angular-mobile.js', - /// 'lib/angular/1.1.5/angular-sanitize.min.js', - /// - /// 'lib/angular/angular-ui-sortable.js', - /// - /// 'lib/angular-dynamic-locale/tmhDynamicLocale.min.js', - /// 'lib [rest of string was truncated]";. + /// 'lib/angular-route/angular-route.js', + /// 'lib/angular-cookies/angular-cookies.js', + /// 'lib/angular-touch/angular-touch.js', + /// 'lib/angular-sanitize/angular-sanitize.js', + /// 'lib/angular-animate/angular-animate.js', + /// [rest of string was truncated]";. /// internal static string JsInitialize { get { @@ -93,10 +93,11 @@ namespace Umbraco.Web.UI.JavaScript { /// /// jQuery(document).ready(function () { /// - /// angular.bootstrap(document, ['umbraco']); + /// angular.bootstrap(document, ['##AngularModule##']); /// /// }); - ///});. + ///}); + ///. /// internal static string Main { get { @@ -104,6 +105,29 @@ namespace Umbraco.Web.UI.JavaScript { } } + /// + /// Looks up a localized string similar to [ + /// '../lib/jquery/jquery.min.js', + /// '../lib/angular/angular.js', + /// '../lib/underscore/underscore-min.js', + /// '../lib/umbraco/Extensions.js', + /// '../js/app.js', + /// '../js/umbraco.resources.js', + /// '../js/umbraco.services.js', + /// '../js/umbraco.interceptors.js', + /// '../ServerVariables', + /// '../lib/signalr/jquery.signalR.js', + /// '../BackOffice/signalr/hubs', + /// '../js/umbraco.canvasdesigner.js' + ///] + ///. + /// + internal static string PreviewInitialize { + get { + return ResourceManager.GetString("PreviewInitialize", resourceCulture); + } + } + /// /// Looks up a localized string similar to //TODO: This would be nicer as an angular module so it can be injected into stuff... that'd be heaps nicer, but ///// how to do that when this is not a regular JS file, it is a server side JS file and RequireJS seems to only want diff --git a/src/Umbraco.Web/UI/JavaScript/Resources.resx b/src/Umbraco.Web/UI/JavaScript/Resources.resx index 2e03928e43..34cea3a2d4 100644 --- a/src/Umbraco.Web/UI/JavaScript/Resources.resx +++ b/src/Umbraco.Web/UI/JavaScript/Resources.resx @@ -124,6 +124,9 @@ Main.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + previewinitialize.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + servervariables.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 diff --git a/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs b/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs index a8cb93c704..30adf2cc3d 100644 --- a/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs +++ b/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs @@ -112,11 +112,11 @@ namespace Umbraco.Web.UI.Pages // Clear content as .NET transfers rendered content. Response.Clear(); - // Some umbraco pages should not be loaded on timeout, but instead reload the main application in the top window. Like the treeview for instance - if (RedirectToUmbraco) - Response.Redirect(SystemDirectories.Umbraco + "/logout.aspx?t=" + Security.GetSessionId(), true); - else - Response.Redirect(SystemDirectories.Umbraco + "/logout.aspx?redir=" + Server.UrlEncode(Request.RawUrl) + "&t=" + Security.GetSessionId(), true); + // Ensure the person is definitely logged out + UmbracoContext.Current.Security.ClearCurrentLogin(); + + // Redirect to the login page + Response.Redirect(SystemDirectories.Umbraco + "#/login", true); } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 79f0fbdafb..f470de9ebd 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -43,6 +43,7 @@ + @@ -59,16 +60,19 @@ - + - + + + 2.6.2.25 + - + @@ -76,10 +80,9 @@ - - + @@ -109,14 +112,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -210,9 +274,7 @@ - - @@ -251,10 +313,8 @@ - - @@ -433,7 +493,7 @@ - + @@ -508,7 +568,7 @@ - + @@ -562,25 +622,24 @@ - - - - - + + + + + - + - + - @@ -592,7 +651,6 @@ - @@ -791,11 +849,11 @@ - - - - - + + + + + @@ -853,7 +911,7 @@ - + @@ -1086,9 +1144,6 @@ - - - @@ -1189,7 +1244,6 @@ - @@ -1257,13 +1311,6 @@ - - Preview.aspx - ASPXCodeBehind - - - Preview.aspx - insertMasterpageContent.aspx ASPXCodeBehind @@ -1304,13 +1351,6 @@ rollBack.aspx - - sendToTranslation.aspx - ASPXCodeBehind - - - sendToTranslation.aspx - Code @@ -1322,42 +1362,12 @@ - - default.aspx - ASPXCodeBehind - - - default.aspx - - - details.aspx - ASPXCodeBehind - - - details.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - xml.aspx - ASPXCodeBehind - - - xml.aspx - - - CheckForUpgrade.asmx @@ -1371,9 +1381,6 @@ nodeSorter.asmx Component - - ASPXCodeBehind - @@ -1413,12 +1420,8 @@ + - - ASPXCodeBehind - - - @@ -1432,7 +1435,6 @@ - ASPXCodeBehind @@ -1444,14 +1446,12 @@ ASPXCodeBehind - ASPXCodeBehind ASPXCodeBehind - @@ -1507,6 +1507,7 @@ +