diff --git a/.gitignore b/.gitignore index 576beabdf9..5b5e7660c5 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ build/*.nupkg src/Umbraco.Tests/config/applications.config src/Umbraco.Tests/config/trees.config src/Umbraco.Web.UI/web.config +src/Umbraco.Web.UI/Config/ClientDependency.config *.orig src/Umbraco.Tests/config/404handlers.config src/Umbraco.Web.UI/[Vv]iews/*.cshtml @@ -132,4 +133,11 @@ src/umbraco.sln.ide/* build/UmbracoCms.*/ src/.vs/ src/Umbraco.Web.UI/umbraco/js/install.loader.js -src/Umbraco.Tests/media \ No newline at end of file +src/Umbraco.Tests/media +tools/docfx/* +apidocs/_site/* +apidocs/api/* +build/docs.zip +build/ui-docs.zip +build/csharp-docs.zip +build/msbuild.log diff --git a/README.md b/README.md index 53070b6917..8f89209328 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,11 @@ Umbraco CMS Umbraco is a free open source Content Management System built on the ASP.NET platform. ## Building Umbraco from source ## -The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `grunt dev` in `src\Umbraco.Web.UI.Client`. +The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `grunt vs` in `src\Umbraco.Web.UI.Client`. -If you're interested in making changes to Belle make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md). Note that you can always [download a nightly build](http://nightly.umbraco.org/umbraco%207.0.0/) so you don't have to build the code yourself. +If you're interested in making changes to Belle without running Visual Studio make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md). + +Note that you can always [download a nightly build (to be found in the artifacts tab)](https://ci.appveyor.com/project/Umbraco/umbraco-cms-hs8dx/history/branch/dev-v7) so you don't have to build the code yourself. ## Watch a introduction video ## diff --git a/apidocs/docfx.filter.yml b/apidocs/docfx.filter.yml new file mode 100644 index 0000000000..e96fbaafff --- /dev/null +++ b/apidocs/docfx.filter.yml @@ -0,0 +1,18 @@ +apiRules: + - include: + uidRegex: ^Umbraco\.Core + - exclude: + uidRegex: ^umbraco\.Web\.org + - include: + uidRegex: ^Umbraco\.Web + - exclude: + hasAttribute: + uid: System.ComponentModel.EditorBrowsableAttribute + ctorArguments: + - System.ComponentModel.EditorBrowsableState.Never + - exclude: + uidRegex: ^umbraco\. + - exclude: + uidRegex: ^CookComputing\. + - exclude: + uidRegex: ^.*$ \ No newline at end of file diff --git a/apidocs/docfx.json b/apidocs/docfx.json new file mode 100644 index 0000000000..520622a0e0 --- /dev/null +++ b/apidocs/docfx.json @@ -0,0 +1,75 @@ +{ + "metadata": [ + { + "src": [ + { + "files": [ + "Umbraco.Core/Umbraco.Core.csproj", + "Umbraco.Web/Umbraco.Web.csproj" + ], + "exclude": [ + "**/obj/**", + "**/bin/**", + "_site/**" + ], + "cwd": "../src" + } + ], + "dest": "../apidocs/api", + "filter": "../apidocs/docfx.filter.yml" + } + ], + "build": { + "content": [ + { + "files": [ + "api/**.yml", + "api/index.md" + ] + }, + { + "files": [ + "articles/**.md", + "articles/**/toc.yml", + "toc.yml", + "*.md" + ], + "exclude": [ + "obj/**", + "_site/**" + ] + } + ], + "resource": [ + { + "files": [ + "images/**" + ], + "exclude": [ + "obj/**", + "_site/**" + ] + } + ], + "overwrite": [ + { + "files": [ + "**.md" + ], + "exclude": [ + "obj/**", + "_site/**" + ] + } + ], + "globalMetadata": { + "_appTitle": "Umbraco c# Api docs", + "_enableSearch": true, + "_disableContribution": false + }, + "dest": "_site", + "template": [ + "default", "umbracotemplate" + ] + } +} \ No newline at end of file diff --git a/apidocs/index.md b/apidocs/index.md new file mode 100644 index 0000000000..b90b816312 --- /dev/null +++ b/apidocs/index.md @@ -0,0 +1,7 @@ + +# Umbraco c# API reference + +## Quick Links: + +### [Umbraco.Core](api/Umbraco.Core.html) docs +### [Umbraco.Web](api/Umbraco.Web.html) docs diff --git a/apidocs/toc.yml b/apidocs/toc.yml new file mode 100644 index 0000000000..6817825066 --- /dev/null +++ b/apidocs/toc.yml @@ -0,0 +1,5 @@ + +- name: Umbraco.Core Documentation + href: https://our.umbraco.org/apidocs/csharp/api/Umbraco.Core.html +- name: Umbraco.Web Documentation + href: https://our.umbraco.org/apidocs/csharp/api/Umbraco.Web.html \ No newline at end of file diff --git a/apidocs/umbracotemplate/partials/class.tmpl.partial b/apidocs/umbracotemplate/partials/class.tmpl.partial new file mode 100644 index 0000000000..9153a863a4 --- /dev/null +++ b/apidocs/umbracotemplate/partials/class.tmpl.partial @@ -0,0 +1,257 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +{{^_disableContribution}} +{{#sourceurl}}{{__global.viewSource}}{{/sourceurl}} +{{/_disableContribution}} +

{{>partials/title}}

+
{{{summary}}}
+
{{{conceptual}}}
+{{#inheritance.0}} +
+
{{__global.inheritance}}
+{{#inheritance}} +
{{{specName.0.value}}}
+{{/inheritance}} +
{{item.name.0.value}}
+
+{{/inheritance.0}} +
{{__global.namespace}}:{{namespace}}
+
{{__global.assembly}}:{{assemblies.0}}.dll
+
{{__global.syntax}}
+
+
{{syntax.content.0.value}}
+
+{{#syntax.parameters.0}} +
{{__global.parameters}}
+ + + + + + + + + +{{/syntax.parameters.0}} +{{#syntax.parameters}} + + + + + +{{/syntax.parameters}} +{{#syntax.parameters.0}} + +
{{__global.type}}{{__global.name}}{{__global.description}}
{{{type.specName.0.value}}}{{{id}}}{{{description}}}
+{{/syntax.parameters.0}} +{{#syntax.return}} +
{{__global.returns}}
+ + + + + + + + + + + + + +
{{__global.type}}{{__global.description}}
{{{type.specName.0.value}}}{{{description}}}
+{{/syntax.return}} +{{#syntax.typeParameters.0}} +
{{__global.typeParameters}}
+ + + + + + + + +{{/syntax.typeParameters.0}} +{{#syntax.typeParameters}} + + + + +{{/syntax.typeParameters}} +{{#syntax.typeParameters.0}} + +
{{__global.name}}{{__global.description}}
{{{id}}}{{{description}}}
+{{/syntax.typeParameters.0}} +{{#remarks}} +
{{__global.remarks}}
+
{{{remarks}}}
+{{/remarks}} +{{#example.0}} +
{{__global.examples}}
+{{/example.0}} +{{#example}} +{{{.}}} +{{/example}} +{{#children}} +

{{>partials/classSubtitle}}

+{{#children}} +{{^_disableContribution}} +{{#sourceurl}} + + {{__global.viewSource}} +{{/sourceurl}} +{{/_disableContribution}} +

{{name.0.value}}

+
{{{summary}}}
+
{{{conceptual}}}
+
{{__global.declaration}}
+{{#syntax}} +
+
{{syntax.content.0.value}}
+
+{{#parameters.0}} +
{{__global.parameters}}
+ + + + + + + + + +{{/parameters.0}} +{{#parameters}} + + + + + +{{/parameters}} +{{#parameters.0}} + +
{{__global.type}}{{__global.name}}{{__global.description}}
{{{type.specName.0.value}}}{{{id}}}{{{description}}}
+{{/parameters.0}} +{{#return}} +
{{__global.returns}}
+ + + + + + + + + + + + + +
{{__global.type}}{{__global.description}}
{{{type.specName.0.value}}}{{{description}}}
+{{/return}} +{{#typeParameters.0}} +
{{__global.typeParameters}}
+ + + + + + + + +{{/typeParameters.0}} +{{#typeParameters}} + + + + +{{/typeParameters}} +{{#typeParameters.0}} + +
{{__global.name}}{{__global.description}}
{{{id}}}{{{description}}}
+{{/typeParameters.0}} +{{#fieldValue}} +
{{__global.fieldValue}}
+ + + + + + + + + + + + + +
{{__global.type}}{{__global.description}}
{{{type.specName.0.value}}}{{{description}}}
+{{/fieldValue}} +{{#propertyValue}} +
{{__global.propertyValue}}
+ + + + + + + + + + + + + +
{{__global.type}}{{__global.description}}
{{{type.specName.0.value}}}{{{description}}}
+{{/propertyValue}} +{{#eventType}} +
{{__global.eventType}}
+ + + + + + + + + + + + + +
{{__global.type}}{{__global.description}}
{{{type.specName.0.value}}}{{{description}}}
+{{/eventType}} +{{/syntax}} +{{#remarks}} +
{{__global.remarks}}
+
{{{remarks}}}
+{{/remarks}} +{{#example.0}} +
{{__global.examples}}
+{{/example.0}} +{{#example}} +{{{.}}} +{{/example}} +{{#exceptions.0}} +
{{__global.exceptions}}
+ + + + + + + + +{{/exceptions.0}} +{{#exceptions}} + + + + +{{/exceptions}} +{{#exceptions.0}} + +
{{__global.type}}{{__global.condition}}
{{{type.specName.0.value}}}{{{description}}}
+{{/exceptions.0}} +{{/children}} +{{/children}} diff --git a/apidocs/umbracotemplate/partials/footer.tmpl.partial b/apidocs/umbracotemplate/partials/footer.tmpl.partial new file mode 100644 index 0000000000..69f51a101f --- /dev/null +++ b/apidocs/umbracotemplate/partials/footer.tmpl.partial @@ -0,0 +1,13 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + diff --git a/apidocs/umbracotemplate/partials/head.tmpl.partial b/apidocs/umbracotemplate/partials/head.tmpl.partial new file mode 100644 index 0000000000..591e1c1885 --- /dev/null +++ b/apidocs/umbracotemplate/partials/head.tmpl.partial @@ -0,0 +1,18 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + + + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + + + + {{#_description}}{{/_description}} + + + + + + + {{#_enableSearch}}{{/_enableSearch}} + diff --git a/apidocs/umbracotemplate/partials/namespace.tmpl.partial b/apidocs/umbracotemplate/partials/namespace.tmpl.partial new file mode 100644 index 0000000000..80ca30799a --- /dev/null +++ b/apidocs/umbracotemplate/partials/namespace.tmpl.partial @@ -0,0 +1,18 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +{{^_disableContribution}} +{{#sourceurl}} +{{__global.viewSource}} +{{/sourceurl}} +{{/_disableContribution}} +

{{>partials/title}}

+
{{{summary}}}
+
{{{conceptual}}}
+
{{{remarks}}}
+{{#children}} +

{{>partials/namespaceSubtitle}}

+ {{#children}} +

{{{specName.0.value}}}

+
{{{summary}}}
+ {{/children}} +{{/children}} diff --git a/apidocs/umbracotemplate/partials/navbar.tmpl.partial b/apidocs/umbracotemplate/partials/navbar.tmpl.partial new file mode 100644 index 0000000000..e9ee0af1c7 --- /dev/null +++ b/apidocs/umbracotemplate/partials/navbar.tmpl.partial @@ -0,0 +1,22 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + diff --git a/apidocs/umbracotemplate/partials/rest.tmpl.partial b/apidocs/umbracotemplate/partials/rest.tmpl.partial new file mode 100644 index 0000000000..4306bf7db1 --- /dev/null +++ b/apidocs/umbracotemplate/partials/rest.tmpl.partial @@ -0,0 +1,101 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +{{^_disableContribution}} +{{#sourceurl}}View Source{{/sourceurl}} +{{/_disableContribution}} +

{{name}}

+{{#summary}} +
{{{summary}}}
+{{/summary}} +{{#description}} +
{{{description}}}
+{{/description}} +{{#conceptual}} +
{{{conceptual}}}
+{{/conceptual}} +{{#children}} +{{^_disableContribution}} +{{#sourceurl}} + + View Source +{{/sourceurl}} +{{/_disableContribution}} +

{{operationId}}

+{{#summary}} +
{{{summary}}}
+{{/summary}} +{{#description}} +
{{{description}}}
+{{/description}} +{{#conceptual}} +
{{{conceptual}}}
+{{/conceptual}} +
Request
+
+
{{operation}} {{path}}
+
+{{#parameters.0}} +
Parameters
+ + + + + + + + + + +{{/parameters.0}} +{{#parameters}} + + + + + + + {{/parameters}} + {{#parameters.0}} + +
NameTypeValueNotes
{{#required}}*{{/required}}{{name}}{{type}}{{default}}{{{description}}}
+{{/parameters.0}} +{{#responses.0}} +
+
Responses
+ + + + + + + + + +{{/responses.0}} +{{#responses}} + + + + + + {{/responses}} + {{#responses.0}} + +
Status CodeDescriptionSamples
{{statusCode}}{{{description}}} + {{#examples}} +
+ Mime type: {{mimeType}} +
+
{{content}}
+ {{/examples}} +
+
+{{/responses.0}} +{{#footer}} + +{{/footer}} +{{/children}} +{{#footer}} + +{{/footer}} + diff --git a/apidocs/umbracotemplate/styles/main.css b/apidocs/umbracotemplate/styles/main.css new file mode 100644 index 0000000000..7756b2f7d4 --- /dev/null +++ b/apidocs/umbracotemplate/styles/main.css @@ -0,0 +1,73 @@ +body { + color: rgba(0,0,0,.8); +} +.navbar-inverse { + background: #a3db78; +} +.navbar-inverse .navbar-nav>li>a, .navbar-inverse .navbar-text { + color: rgba(0,0,0,.8); +} + +.navbar-inverse { + border-color: transparent; +} + +.sidetoc { + background-color: #f5fbf1; +} +body .toc { + background-color: #f5fbf1; +} +.sidefilter { + background-color: #daf0c9; +} +.subnav { + background-color: #f5fbf1; +} + +.navbar-inverse .navbar-nav>.active>a { + color: rgba(0,0,0,.8); + background-color: #daf0c9; +} + +.navbar-inverse .navbar-nav>.active>a:focus, .navbar-inverse .navbar-nav>.active>a:hover { + color: rgba(0,0,0,.8); + background-color: #daf0c9; +} + +.btn-primary { + color: rgba(0,0,0,.8); + background-color: #fff; + border-color: rgba(0,0,0,.8); +} +.btn-primary:hover { + background-color: #daf0c9; + color: rgba(0,0,0,.8); + border-color: rgba(0,0,0,.8); +} + +.toc .nav > li > a { + color: rgba(0,0,0,.8); +} + +button, a { + color: #f36f21; +} + +button:hover, +button:focus, +a:hover, +a:focus { + color: #143653; + text-decoration: none; +} + +.navbar-header .navbar-brand { + background: url(https://our.umbraco.org/assets/images/logo.svg) left center no-repeat; + background-size: 40px auto; + width:50px; +} + +.toc .nav > li.active > a { + color: #f36f21; +} diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..eaba82e666 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,46 @@ +version: '{build}' +shallow_clone: true +build_script: +- cmd: >- + cd build + + SET "release=" + + FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED release SET "release=%%i" + + SET nuGetFolder=C:\Users\appveyor\.nuget\packages + + ..\src\.nuget\NuGet.exe sources Add -Name MyGetUmbracoCore -Source https://www.myget.org/F/umbracocore/api/v2/ >NUL + + ..\src\.nuget\NuGet.exe install ..\src\Umbraco.Web.UI\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet + + IF EXIST ..\src\umbraco.businesslogic\packages.config ..\src\.nuget\NuGet.exe install ..\src\umbraco.businesslogic\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet + + ..\src\.nuget\NuGet.exe install ..\src\Umbraco.Core\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet + + ECHO Building Release %release% build%APPVEYOR_BUILD_NUMBER% + + SET PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH% + + SET MSBUILD="C:\Program Files (x86)\MSBuild\14.0\Bin\MsBuild.exe" + + XCOPY "..\src\Umbraco.Tests\unit-test-log4net.CI.config" "..\src\Umbraco.Tests\unit-test-log4net.config" /Y + + %MSBUILD% "..\src\Umbraco.Tests\Umbraco.Tests.csproj" /consoleloggerparameters:Summary;ErrorsOnly + + build.bat nopause %release% build%APPVEYOR_BUILD_NUMBER% + + ECHO %PATH% +test: + assemblies: src\Umbraco.Tests\bin\Debug\Umbraco.Tests.dll +artifacts: +- path: build\UmbracoCms.* +- path: build\msbuild.log +notifications: +- provider: Slack + auth_token: + secure: v2csJi2V5ghR0rPdODK8GJdOGNCA+XaK84iQ9MdPOClqB+VU+40ybdKp6gPirGSH + channel: '#build-umbraco-core' + on_build_success: false + on_build_failure: true + on_build_status_changed: false \ No newline at end of file diff --git a/build/ApiDocs/TOC.css b/build/ApiDocs/TOC.css deleted file mode 100644 index 9c32aba214..0000000000 --- a/build/ApiDocs/TOC.css +++ /dev/null @@ -1,170 +0,0 @@ -/* File : TOC.css -// Author : Eric Woodruff (Eric@EWoodruff.us) -// Updated : 09/07/2007 -// -// Stylesheet for the table of content -*/ - -* -{ - margin: 0px 0px 0px 0px; - padding: 0px 0px 0px 0px; -} - -body -{ - font-family: Segoe UI, Arial, Verdana, Helvetica, sans-serif; - font-size: 0.9em; - background-color: white; - color: White; - overflow: hidden; -} - -input -{ - padding:5px; - margin: 3px 0px 3px 0px -} - -img -{ - border: 0; - margin-left: 5px; - margin-right: 2px; -} - -img.TreeNodeImg -{ - cursor: pointer; -} - -img.TOCLink -{ - cursor: pointer; - margin-left: 0; - margin-right: 0; -} - -a.SelectedNode, a.UnselectedNode -{ - color: #333; - text-decoration: none; - padding: 1px 3px 1px 3px; - white-space: nowrap; -} - -a.SelectedNode -{ - background-color: #ffffff; - border: solid 1px #999999; - padding: 0px 2px 0px 2px; -} - -a.UnselectedNode:hover, a.SelectedNode:hover -{ - background-color: #cccccc; - border: solid 1px #999999; - padding: 0px 2px 0px 2px; -} - -.Visible -{ - display: block; - margin-left: 2em; -} - -.Hidden -{ - display: none; -} - -.Tree -{ - background-color: #fff; - color: #333; - width: 300px; - overflow: auto; -} - -.TreeNode, .TreeItem -{ - white-space: nowrap; - margin: 2px 2px 2px 2px; -} - -.TOCDiv -{ - position: relative; - float: left; - width: 300px; - height: 100%; -} - -.TOCSizer -{ - clear: none; - float: left; - width: 10px; - height: 100%; - background-image: url("Splitter.gif"); - background-position:center center; - background-repeat:no-repeat; - position: relative; - cursor: w-resize; - border-left: solid 1px #CCC; -} - -.TopicContent -{ - position: relative; - float: right; - background-color: white; - height: 100%; -} - -.SearchOpts -{ - padding: 5px 5px 10px 5px; - color: black; - width: 300px; - border-bottom: solid lightgrey 1px; - height: 110px !important; -} - -.NavOpts -{ - padding: 5px 5px 6px 5px; - color: black; - width: 300px; - border-bottom: solid lightgrey 1px; -} - -.NavOpts img { - display:inline-block; - margin: 0px 5px 0px 5px; -} - -.IndexOpts -{ - padding: 5px 5px 6px 5px; - color: black; - width: 300px; - border-bottom: solid lightgrey 1px; -} - -.IndexItem -{ - white-space: nowrap; - margin: 2px 2px 2px 2px; -} - -.IndexSubItem -{ - white-space: nowrap; - margin: 2px 2px 2px 12px; -} - -.PaddedText -{ - margin: 10px 10px 10px 10px; -} diff --git a/build/ApiDocs/csharp-api-docs.shfbproj b/build/ApiDocs/csharp-api-docs.shfbproj deleted file mode 100644 index f635f2e2ee..0000000000 --- a/build/ApiDocs/csharp-api-docs.shfbproj +++ /dev/null @@ -1,172 +0,0 @@ - - - - - Debug - AnyCPU - 2.0 - {cb4d85f1-7390-40ee-b41f-a724bb8fddea} - 1.9.5.0 - - Documentation - Documentation - Documentation - - .NET Framework 4.5 - .\Output\ - UmbracoClassLibrary - en-US - OnlyErrors - csharp-api-docs.log - Website - True - False - False - False - True - CSharp - Blank - False - VS2010 - False - Guid - Umbraco .Net Class Library - AboveNamespaces - Attributes, InheritedMembers, InheritedFrameworkMembers, Protected, SealedProtected - - - - - - - None - None - False - True - Summary, AutoDocumentCtors, AutoDocumentDispose - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/Build.bat b/build/Build.bat index 1167d9fcb6..fc0d8a69ea 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -11,20 +11,28 @@ FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED release SE FOR /F "skip=2 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED comment SET "comment=%%i" REM If there's arguments on the command line overrule UmbracoVersion.txt and use that as the version -IF [%1] NEQ [] (SET release=%1) -IF [%2] NEQ [] (SET comment=%2) ELSE (IF [%1] NEQ [] (SET "comment=")) +IF [%2] NEQ [] (SET release=%2) +IF [%3] NEQ [] (SET comment=%3) ELSE (IF [%2] NEQ [] (SET "comment=")) + +REM Get the "is continuous integration" from the parameters +SET "isci=0" +IF [%1] NEQ [] (SET isci=1) SET version=%release% - IF [%comment%] EQU [] (SET version=%release%) ELSE (SET version=%release%-%comment%) + +ECHO. ECHO Building Umbraco %version% +ECHO. ReplaceIISExpressPortNumber.exe ..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj %release% +ECHO. ECHO Removing the belle build folder and bower_components folder to make sure everything is clean as a whistle RD ..\src\Umbraco.Web.UI.Client\build /Q /S RD ..\src\Umbraco.Web.UI.Client\bower_components /Q /S +ECHO. ECHO Removing existing built files to make sure everything is clean as a whistle RMDIR /Q /S _BuildOutput DEL /F /Q UmbracoCms.*.zip @@ -32,27 +40,56 @@ DEL /F /Q UmbracoExamine.*.zip DEL /F /Q UmbracoCms.*.nupkg DEL /F /Q webpihash.txt +ECHO. ECHO Making sure Git is in the path so that the build can succeed CALL InstallGit.cmd -ECHO Performing MSBuild and producing Umbraco binaries zip files -%windir%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe "Build.proj" /p:BUILD_RELEASE=%release% /p:BUILD_COMMENT=%comment% /verbosity:minimal +REM Adding the default Git path so that if it's installed it can actually be found +REM This is necessary because SETLOCAL is on in InstallGit.cmd so that one might find Git, +REM but the path setting is lost due to SETLOCAL +path=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH% + +ECHO. +ECHO Making sure we have a web.config +IF NOT EXIST %CD%\..\src\Umbraco.Web.UI\web.config COPY %CD%\..\src\Umbraco.Web.UI\web.Template.config %CD%\..\src\Umbraco.Web.UI\web.config + +ECHO. +ECHO. +ECHO Performing MSBuild and producing Umbraco binaries zip files +ECHO This takes a few minutes and logging is set to report warnings +ECHO and errors only so it might seems like nothing is happening for a while. +ECHO You can check the msbuild.log file for progress. +ECHO. +%windir%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe "Build.proj" /p:BUILD_RELEASE=%release% /p:BUILD_COMMENT=%comment% /p:NugetPackagesDirectory=%nuGetFolder% /consoleloggerparameters:Summary;ErrorsOnly;WarningsOnly /fileLogger +IF ERRORLEVEL 1 GOTO :error + +ECHO. ECHO Setting node_modules folder to hidden to prevent VS13 from crashing on it while loading the websites project attrib +h ..\src\Umbraco.Web.UI.Client\node_modules +ECHO. ECHO Adding Web.config transform files to the NuGet package REN .\_BuildOutput\WebApp\Views\Web.config Web.config.transform REN .\_BuildOutput\WebApp\Xslt\Web.config Web.config.transform +ECHO. ECHO Packing the NuGet release files ..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.Core.nuspec -Version %version% -Symbols -Verbosity quiet ..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.nuspec -Version %version% -Verbosity quiet - -IF ERRORLEVEL 1 GOTO :showerror +IF ERRORLEVEL 1 GOTO :error -ECHO No errors were detected but you still may see some in the output, then it's time to investigate. -ECHO You might see some warnings but that is completely normal. +:success +ECHO. +ECHO No errors were detected! +ECHO There may still be some in the output, which you would need to investigate. +ECHO Warnings are usually normal. GOTO :EOF -:showerror -PAUSE +:error + +ECHO. +ECHO Errors were detected! + +REM don't pause if continuous integration else the build server waits forever +REM before cancelling the build (and, there is noone to read the output anyways) +IF isci NEQ 1 PAUSE diff --git a/build/BuildBelle.bat b/build/BuildBelle.bat index 6c11cc9fc5..8a07ee380a 100644 --- a/build/BuildBelle.bat +++ b/build/BuildBelle.bat @@ -23,6 +23,7 @@ ECHO Change directory to %CD%\..\src\Umbraco.Web.UI.Client\ CD %CD%\..\src\Umbraco.Web.UI.Client\ ECHO Do npm install and the grunt build of Belle +call npm cache clean --quiet call npm install --quiet call npm install -g grunt-cli --quiet call npm install -g bower --quiet diff --git a/build/BuildDocs.bat b/build/BuildDocs.bat new file mode 100644 index 0000000000..9d0a04e1cd --- /dev/null +++ b/build/BuildDocs.bat @@ -0,0 +1,20 @@ +@ECHO OFF +SETLOCAL + +SET release=%1 +ECHO Installing Npm NuGet Package + +SET nuGetFolder=%CD%\..\src\packages\ +ECHO Configured packages folder: %nuGetFolder% +ECHO Current folder: %CD% + +%CD%\..\src\.nuget\NuGet.exe install Npm.js -OutputDirectory %nuGetFolder% -Verbosity quiet + +for /f "delims=" %%A in ('dir %nuGetFolder%node.js.* /b') do set "nodePath=%nuGetFolder%%%A\" +for /f "delims=" %%A in ('dir %nuGetFolder%npm.js.* /b') do set "npmPath=%nuGetFolder%%%A\tools\" + +ECHO Adding Npm and Node to path +REM SETLOCAL is on, so changes to the path not persist to the actual user's path +PATH=%npmPath%;%nodePath%;%PATH% + +Powershell.exe -ExecutionPolicy Unrestricted -File .\BuildDocs.ps1 \ No newline at end of file diff --git a/build/BuildDocs.ps1 b/build/BuildDocs.ps1 index 6f46a43fde..dcb3a85cc1 100644 --- a/build/BuildDocs.ps1 +++ b/build/BuildDocs.ps1 @@ -1,27 +1,100 @@ -##We cannot continue if sandcastle is not installed determined by env variable: SHFBROOT +$PSScriptFilePath = (Get-Item $MyInvocation.MyCommand.Path); +$RepoRoot = (get-item $PSScriptFilePath).Directory.Parent.FullName; +$SolutionRoot = Join-Path -Path $RepoRoot "src"; +$ToolsRoot = Join-Path -Path $RepoRoot "tools"; +$DocFx = Join-Path -Path $ToolsRoot "docfx\docfx.exe" +$DocFxFolder = (Join-Path -Path $ToolsRoot "docfx") +$DocFxJson = Join-Path -Path $RepoRoot "apidocs\docfx.json" +$7Zip = Join-Path -Path $ToolsRoot "7zip\7za.exe" +$DocFxSiteOutput = Join-Path -Path $RepoRoot "apidocs\_site\*.*" +$NgDocsSiteOutput = Join-Path -Path $RepoRoot "src\Umbraco.Web.UI.Client\docs\api\*.*" +$ProgFiles86 = [Environment]::GetEnvironmentVariable("ProgramFiles(x86)"); +$MSBuild = "$ProgFiles86\MSBuild\14.0\Bin\MSBuild.exe" -if (-not (Test-Path Env:\SHFBROOT)) -{ - throw "The docs cannot be build, install Sandcastle help file builder" + +################ Do the UI docs + +"Changing to Umbraco.Web.UI.Client folder" +cd .. +cd src\Umbraco.Web.UI.Client +Write-Host $(Get-Location) + +"Creating build folder so MSBuild doesn't run the whole grunt build" +if (-Not (Test-Path "build")) { + md "build" } -$PSScriptFilePath = (Get-Item $MyInvocation.MyCommand.Path).FullName -$BuildRoot = Split-Path -Path $PSScriptFilePath -Parent -$OutputPath = Join-Path -Path $BuildRoot -ChildPath "ApiDocs\Output" -$ProjFile = Join-Path -Path $BuildRoot -ChildPath "ApiDocs\csharp-api-docs.shfbproj" +"Installing node" +# Check if Install-Product exists, should only exist on the build server +if (Get-Command Install-Product -errorAction SilentlyContinue) +{ + Install-Product node '' +} -"Building docs with project file: $ProjFile" +"Installing node modules" +& npm install -$MSBuild = "$Env:SYSTEMROOT\Microsoft.NET\Framework\v4.0.30319\msbuild.exe" +"Installing grunt" +& npm install -g grunt-cli -# build it! -& $MSBuild "$ProjFile" +"Moving back to build folder" +cd .. +cd .. +cd build +Write-Host $(Get-Location) -# remove files left over -Remove-Item $BuildRoot\* -include csharp-api-docs.shfbproj_* + & grunt --gruntfile ../src/umbraco.web.ui.client/gruntfile.js docs -# copy our custom styles in -Copy-Item $BuildRoot\ApiDocs\TOC.css $OutputPath\TOC.css +# change baseUrl +$BaseUrl = "https://our.umbraco.org/apidocs/ui/" +$IndexPath = "../src/umbraco.web.ui.client/docs/api/index.html" +(Get-Content $IndexPath).replace('location.href.replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath +# zip it -"" -"Done!" \ No newline at end of file +& $7Zip a -tzip ui-docs.zip $NgDocsSiteOutput -r + +################ Do the c# docs + +# Build the solution in debug mode +$SolutionPath = Join-Path -Path $SolutionRoot -ChildPath "umbraco.sln" +& $MSBuild "$SolutionPath" /p:Configuration=Debug /maxcpucount /t:Clean +if (-not $?) +{ + throw "The MSBuild process returned an error code." +} +& $MSBuild "$SolutionPath" /p:Configuration=Debug /maxcpucount +if (-not $?) +{ + throw "The MSBuild process returned an error code." +} + +# Go get docfx if we don't hae it +$FileExists = Test-Path $DocFx +If ($FileExists -eq $False) { + + If(!(Test-Path $DocFxFolder)) + { + New-Item $DocFxFolder -type directory + } + + $DocFxZip = Join-Path -Path $ToolsRoot "docfx\docfx.zip" + $DocFxSource = "https://github.com/dotnet/docfx/releases/download/v1.9.4/docfx.zip" + Invoke-WebRequest $DocFxSource -OutFile $DocFxZip + + #unzip it + & $7Zip e $DocFxZip "-o$DocFxFolder" +} + +#clear site +If(Test-Path(Join-Path -Path $RepoRoot "apidocs\_site")) +{ + Remove-Item $DocFxSiteOutput -recurse +} + +# run it! +& $DocFx metadata $DocFxJson +& $DocFx build $DocFxJson + +# zip it + +& $7Zip a -tzip csharp-docs.zip $DocFxSiteOutput -r diff --git a/build/InstallGit.cmd b/build/InstallGit.cmd index b6ba71df9b..e009e2594e 100644 --- a/build/InstallGit.cmd +++ b/build/InstallGit.cmd @@ -1,23 +1,33 @@ @ECHO OFF SETLOCAL -REM SETLOCAL is on, so changes to the path not persist to the actual user's path + :: SETLOCAL is on, so changes to the path not persist to the actual user's path -git.exe 2> NUL -if %ERRORLEVEL%==9009 GOTO :trydefaultpath +git.exe --version +IF %ERRORLEVEL%==9009 GOTO :trydefaultpath GOTO :EOF + :: Git is installed, no need to to anything else :trydefaultpath -path=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH% -git.exe 2> NUL -if %ERRORLEVEL%==9009 GOTO :showerror +PATH=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH% +git.exe --version +IF %ERRORLEVEL%==9009 GOTO :showerror GOTO :EOF + :: Git is installed, no need to to anything else :showerror ECHO Git is not in your path and could not be found in C:\Program Files (x86)\Git\cmd nor in C:\Program Files\Git\cmd -set /p install=" Do you want to install Git through Chocolatey [y/n]? " %=% -if %install%==y ( +SET /p install=" Do you want to install Git through Chocolatey [y/n]? " %=% +IF %install%==y ( + :: Create a temporary batch file to execute either after elevating to admin or as-is when the user is already admin + ECHO @ECHO OFF > "%temp%\ChocoInstallGit.cmd" + ECHO SETLOCAL >> "%temp%\ChocoInstallGit.cmd" + ECHO ECHO Installing Chocolatey first >> "%temp%\ChocoInstallGit.cmd" + ECHO @powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" >> "%temp%\ChocoInstallGit.cmd" + ECHO SET PATH=%%PATH%%;%%ALLUSERSPROFILE%%\chocolatey\bin >> "%temp%\ChocoInstallGit.cmd" + ECHO choco install git -y >> "%temp%\ChocoInstallGit.cmd" + GOTO :installgit -) else ( +) ELSE ( GOTO :cantcontinue ) @@ -26,7 +36,28 @@ ECHO Can't complete the build without Git being in the path. Please add it to be GOTO :EOF :installgit -ECHO Installing Chocolatey first -@powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin -ECHO Installing Git through Chocolatey -choco install git \ No newline at end of file +pushd %~dp0 + :: Running prompt elevated + +:: --> Check for permissions +>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system" + +:: --> If error flag set, we do not have admin. +IF '%errorlevel%' NEQ '0' ( + GOTO UACPrompt +) ELSE ( GOTO gotAdmin ) + +:UACPrompt + ECHO You're not currently running this with admin privileges, we'll now try to execute the install of Git through Chocolatey after elevating to admin privileges + ECHO Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs" + ECHO UAC.ShellExecute "%temp%\ChocoInstallGit.cmd", "", "", "runas", 1 >> "%temp%\getadmin.vbs" + + "%temp%\getadmin.vbs" + EXIT /B + +:gotAdmin + IF EXIST "%temp%\getadmin.vbs" ( DEL "%temp%\getadmin.vbs" ) + pushd "%CD%" + CD /D "%~dp0" + + CALL "%temp%\ChocoInstallGit.cmd" \ No newline at end of file diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 72733c5800..dbc7be00e4 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -27,15 +27,17 @@ - + + - - - - + + + + + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 66e82ac488..6903c34d8d 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -17,7 +17,7 @@ - + @@ -32,6 +32,7 @@ + diff --git a/build/NuSpecs/tools/Dashboard.config.install.xdt b/build/NuSpecs/tools/Dashboard.config.install.xdt index a77632926c..8368870186 100644 --- a/build/NuSpecs/tools/Dashboard.config.install.xdt +++ b/build/NuSpecs/tools/Dashboard.config.install.xdt @@ -21,6 +21,12 @@
+ + developer + +
+ +
views/dashboard/developer/developerdashboardvideos.html @@ -30,16 +36,21 @@
- + views/dashboard/developer/examinemanagement.html - - + + - views/dashboard/developer/xmldataintegrityreport.html + views/dashboard/developer/healthcheck.html - + + + + views/dashboard/developer/redirecturls.html + +
@@ -52,19 +63,6 @@
-
- - - - admin - - - views/dashboard/default/startupdashboardintro.html - - - -
-
@@ -80,5 +78,6 @@
+
\ No newline at end of file diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index dfb9925840..987db6c3d6 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -292,6 +292,8 @@ + + diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1 index de7a6cc16e..0e62fb0749 100644 --- a/build/NuSpecs/tools/install.ps1 +++ b/build/NuSpecs/tools/install.ps1 @@ -77,8 +77,33 @@ if ($project) { { $packageWebConfigSource = Join-Path $installPath "UmbracoFiles\Web.config" Copy-Item $packageWebConfigSource $destinationWebConfig -Force - } + # Copy files that don't get automatically copied for Website projects + # We do this here, when copyWebconfig is true because we only want to do it for new installs + # If this is an upgrade then the files should already be there + $splashesSource = Join-Path $installPath "UmbracoFiles\Config\splashes\*.*" + $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 + } else { + $upgradeViewSource = Join-Path $umbracoFolderSource "Views\install\*" + $upgradeView = Join-Path $umbracoFolder "Views\install\" + Write-Host "Copying2 ${upgradeViewSource} to ${upgradeView}" + Copy-Item $upgradeViewSource $upgradeView -Force + } + $installFolder = Join-Path $projectPath "Install" if(Test-Path $installFolder) { Remove-Item $installFolder -Force -Recurse -Confirm:$false diff --git a/build/NuSpecs/tools/trees.config.install.xdt b/build/NuSpecs/tools/trees.config.install.xdt index 580c619547..a9fbf07762 100644 --- a/build/NuSpecs/tools/trees.config.install.xdt +++ b/build/NuSpecs/tools/trees.config.install.xdt @@ -55,21 +55,29 @@ xdt:Transform="SetAttributes()" /> - - + xdt:Transform="Remove" /> + + + + + + + + - - diff --git a/src/SQLCE4Umbraco/SqlCEHelper.cs b/src/SQLCE4Umbraco/SqlCEHelper.cs index 26a781360e..ab6f686c21 100644 --- a/src/SQLCE4Umbraco/SqlCEHelper.cs +++ b/src/SQLCE4Umbraco/SqlCEHelper.cs @@ -40,15 +40,10 @@ namespace SqlCE4Umbraco var localConnection = new SqlCeConnection(ConnectionString); if (!System.IO.File.Exists(ReplaceDataDirectory(localConnection.Database))) { - var sqlCeEngine = new SqlCeEngine(ConnectionString); - sqlCeEngine.CreateDatabase(); - - // SD: Pretty sure this should be in a using clause but i don't want to cause unknown side-effects here - // since it's been like this for quite some time - //using (var sqlCeEngine = new SqlCeEngine(ConnectionString)) - //{ - // sqlCeEngine.CreateDatabase(); - //} + using (var sqlCeEngine = new SqlCeEngine(ConnectionString)) + { + sqlCeEngine.CreateDatabase(); + } } } diff --git a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs index ca1f4e85a0..792b5982b1 100644 --- a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs @@ -9,6 +9,9 @@ namespace Umbraco.Core.Cache /// /// A cache provider that caches items in the HttpContext.Items /// + /// + /// If the Items collection is null, then this provider has no effect + /// internal class HttpRequestCacheProvider : DictionaryCacheProviderBase { // context provider @@ -34,6 +37,11 @@ namespace Umbraco.Core.Cache get { return _context != null ? _context.Items : HttpContext.Current.Items; } } + private bool HasContextItems + { + get { return (_context != null && _context.Items != null) || HttpContext.Current != null; } + } + // for unit tests public HttpRequestCacheProvider(HttpContextBase context) { @@ -50,18 +58,23 @@ namespace Umbraco.Core.Cache protected override IEnumerable GetDictionaryEntries() { const string prefix = CacheItemPrefix + "-"; + + if (HasContextItems == false) return Enumerable.Empty(); + return ContextItems.Cast() .Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix)); } protected override void RemoveEntry(string key) { + if (HasContextItems == false) return; + ContextItems.Remove(key); } protected override object GetEntry(string key) { - return ContextItems[key]; + return HasContextItems ? ContextItems[key] : null; } #region Lock @@ -81,7 +94,9 @@ namespace Umbraco.Core.Cache get { - return new MonitorLock(ContextItems.SyncRoot); + return HasContextItems + ? (IDisposable) new MonitorLock(ContextItems.SyncRoot) + : new NoopLocker(); } } @@ -91,6 +106,9 @@ namespace Umbraco.Core.Cache public override object GetCacheItem(string cacheKey, Func getCacheItem) { + //no place to cache so just return the callback result + if (HasContextItems == false) return getCacheItem(); + cacheKey = GetCacheKey(cacheKey); Lazy result; @@ -128,5 +146,10 @@ namespace Umbraco.Core.Cache #region Insert #endregion + private class NoopLocker : DisposableObject + { + protected override void DisposeResources() + { } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/NullCacheProvider.cs b/src/Umbraco.Core/Cache/NullCacheProvider.cs index f4e449499b..6b797307ac 100644 --- a/src/Umbraco.Core/Cache/NullCacheProvider.cs +++ b/src/Umbraco.Core/Cache/NullCacheProvider.cs @@ -5,38 +5,31 @@ using System.Web.Caching; namespace Umbraco.Core.Cache { - internal class NullCacheProvider : IRuntimeCacheProvider + /// + /// Represents a cache provider that does not cache anything. + /// + public class NullCacheProvider : IRuntimeCacheProvider { public virtual void ClearAllCache() - { - } + { } public virtual void ClearCacheItem(string key) - { - } + { } public virtual void ClearCacheObjectTypes(string typeName) - { - } + { } public virtual void ClearCacheObjectTypes() - { - } + { } public virtual void ClearCacheObjectTypes(Func predicate) - { - } - - - + { } public virtual void ClearCacheByKeySearch(string keyStartsWith) - { - } + { } public virtual void ClearCacheByKeyExpression(string regexString) - { - } + { } public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { @@ -64,8 +57,6 @@ namespace Umbraco.Core.Cache } public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) - { - - } + { } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs index 8afa5639f1..feccba03b4 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs @@ -1,23 +1,23 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; -using System.Reflection; using System.Runtime.Caching; using System.Text.RegularExpressions; using System.Threading; using System.Web.Caching; -using Umbraco.Core.Logging; using CacheItemPriority = System.Web.Caching.CacheItemPriority; namespace Umbraco.Core.Cache { /// + /// Represents a cache provider that caches item in a . /// A cache provider that wraps the logic of a System.Runtime.Caching.ObjectCache /// - internal class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider + /// The is created with name "in-memory". That name is + /// used to retrieve configuration options. It does not identify the memory cache, i.e. + /// each instance of this class has its own, independent, memory cache. + public class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider { - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); internal ObjectCache MemoryCache; diff --git a/src/Umbraco.Core/Cache/StaticCacheProvider.cs b/src/Umbraco.Core/Cache/StaticCacheProvider.cs index 9c448efa6a..c7fd00d39a 100644 --- a/src/Umbraco.Core/Cache/StaticCacheProvider.cs +++ b/src/Umbraco.Core/Cache/StaticCacheProvider.cs @@ -3,14 +3,13 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -using System.Web.Caching; namespace Umbraco.Core.Cache { /// - /// A cache provider that statically caches everything in an in memory dictionary + /// Represents a cache provider that statically caches item in a concurrent dictionary. /// - internal class StaticCacheProvider : ICacheProvider + public class StaticCacheProvider : ICacheProvider { internal readonly ConcurrentDictionary StaticCache = new ConcurrentDictionary(); @@ -75,7 +74,6 @@ namespace Umbraco.Core.Cache public virtual object GetCacheItem(string cacheKey, Func getCacheItem) { return StaticCache.GetOrAdd(cacheKey, key => getCacheItem()); - } - + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index 1b1404a897..d743daca6a 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -291,7 +291,19 @@ namespace Umbraco.Core.Configuration.UmbracoSettings true); } } - + + [ConfigurationProperty("EnableInheritedMediaTypes")] + internal InnerTextConfigurationElement EnableInheritedMediaTypes + { + get + { + return new OptionalInnerTextConfigurationElement( + (InnerTextConfigurationElement)this["EnableInheritedMediaTypes"], + //set the default + true); + } + } + string IContentSection.NotificationEmailAddress { get { return Notifications.NotificationEmailAddress; } @@ -431,5 +443,10 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { get { return EnableInheritedDocumentTypes; } } + + bool IContentSection.EnableInheritedMediaTypes + { + get { return EnableInheritedMediaTypes; } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index ebdd9ae637..3d5e4435b6 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -61,5 +61,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings string DefaultDocumentTypeProperty { get; } bool EnableInheritedDocumentTypes { get; } + + bool EnableInheritedMediaTypes { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs index 2998fc2f78..9eb6d02aa7 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs @@ -9,6 +9,8 @@ bool DisableAlternativeTemplates { get; } bool DisableFindContentByIdPath { get; } + + bool DisableRedirectUrlTracking { get; } string UrlProviderMode { get; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs index 1ed9bc034c..82f5d46b28 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs @@ -27,6 +27,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return (bool) base["disableFindContentByIdPath"]; } } + [ConfigurationProperty("disableRedirectUrlTracking", DefaultValue = "false")] + public bool DisableRedirectUrlTracking + { + get { return (bool) base["disableRedirectUrlTracking"]; } + } + [ConfigurationProperty("urlProviderMode", DefaultValue = "AutoLegacy")] public string UrlProviderMode { diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index 12f7076fc4..fb1fb42044 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -73,6 +73,11 @@ /// public const string DataTypes = "dataTypes"; + /// + /// alias for the packages tree + /// + public const string Packages = "packager"; + /// /// alias for the dictionary tree. /// diff --git a/src/Umbraco.Core/Constants-Examine.cs b/src/Umbraco.Core/Constants-Examine.cs index 4ff6115749..9b1de812f7 100644 --- a/src/Umbraco.Core/Constants-Examine.cs +++ b/src/Umbraco.Core/Constants-Examine.cs @@ -1,15 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Core +namespace Umbraco.Core { public static partial class Constants { public static class Examine { + /// + /// The alias of the internal member indexer + /// + public const string InternalMemberIndexer = "InternalMemberIndexer"; + + /// + /// The alias of the internal content indexer + /// + public const string InternalIndexer = "InternalIndexer"; + + /// + /// The alias of the external content indexer + /// + public const string ExternalIndexer = "ExternalIndexer"; /// /// The alias of the internal member searcher /// @@ -19,6 +27,11 @@ namespace Umbraco.Core /// The alias of the internal content searcher /// public const string InternalSearcher = "InternalSearcher"; + + /// + /// The alias of the external content searcher + /// + public const string ExternalSearcher = "ExternalSearcher"; } } } diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index 560cd4b306..7ec45db7be 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -143,7 +143,10 @@ namespace Umbraco.Core /// public const string LockObject = "87A9F1FF-B1E4-4A25-BABB-465A4A47EC41"; - + /// + /// Guid for a Lock object. + /// + public static readonly Guid LockObjectGuid = new Guid(LockObject); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 2f7d247b36..80f118b58e 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core { /// /// Used to prefix generic properties that are internal content properties - /// + /// public const string InternalGenericPropertiesPrefix = "_umb_"; /// @@ -74,7 +74,7 @@ namespace Umbraco.Core /// [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] public const string DictionaryPicker = "17B70066-F764-407D-AB05-3717F1E1C513"; - + /// /// Guid for the Dropdown list datatype. /// @@ -352,7 +352,7 @@ namespace Umbraco.Core /// [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] public const string UltimatePicker = "CDBF0B5D-5CB2-445F-BC12-FCAAEC07CF2C"; - + /// /// Guid for the UltraSimpleEditor datatype. /// @@ -369,7 +369,7 @@ namespace Umbraco.Core /// [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] public const string UmbracoUserControlWrapper = "D15E1281-E456-4B24-AA86-1DDA3E4299D5"; - + /// /// Guid for the Upload field datatype. /// @@ -419,6 +419,15 @@ namespace Umbraco.Core /// Alias for the email address property editor /// public const string EmailAddressAlias = "Umbraco.EmailAddress"; + + public static class PreValueKeys + { + /// + /// Pre-value name used to indicate a field that can be used to override the database field to which data for the associated + /// property is saved + /// + public const string DataValueType = "umbracoDataValueType"; + } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index 82e3a1ff3f..4a30db9cd8 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -1,33 +1,40 @@ namespace Umbraco.Core { - public static partial class Constants - { - /// - /// Defines the identifiers for Umbraco system nodes. - /// - public static class System - { - /// - /// The integer identifier for global system root node. - /// - public const int Root = -1; + public static partial class Constants + { + /// + /// Defines the identifiers for Umbraco system nodes. + /// + public static class System + { + /// + /// The integer identifier for global system root node. + /// + public const int Root = -1; - /// - /// The integer identifier for content's recycle bin. - /// - public const int RecycleBinContent = -20; + /// + /// The integer identifier for content's recycle bin. + /// + public const int RecycleBinContent = -20; - /// - /// The integer identifier for media's recycle bin. - /// - public const int RecycleBinMedia = -21; + /// + /// The integer identifier for media's recycle bin. + /// + public const int RecycleBinMedia = -21; - public const int DefaultContentListViewDataTypeId = -95; + public const int DefaultContentListViewDataTypeId = -95; public const int DefaultMediaListViewDataTypeId = -96; public const int DefaultMembersListViewDataTypeId = -97; // identifiers for lock objects - public const int ServersLock = -331; - } - } + public const int ServersLock = -331; + } + + public static class DatabaseProviders + { + public const string SqlCe = "System.Data.SqlServerCe.4.0"; + public const string SqlServer = "System.Data.SqlClient"; + public const string MySql = "MySql.Data.MySqlClient"; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 7f8cbbf0f7..05aba6b97d 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -135,7 +135,7 @@ namespace Umbraco.Core if (string.IsNullOrEmpty(_providerName) == false) return _providerName; - _providerName = "System.Data.SqlClient"; + _providerName = Constants.DatabaseProviders.SqlServer; if (ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName] != null) { if (string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName) == false) @@ -212,10 +212,10 @@ namespace Umbraco.Core { var provider = DbConnectionExtensions.DetectProviderFromConnectionString(connectionString); var databaseProvider = provider.ToString(); - var providerName = "System.Data.SqlClient"; + var providerName = Constants.DatabaseProviders.SqlServer; if (databaseProvider.ToLower().Contains("mysql")) { - providerName = "MySql.Data.MySqlClient"; + providerName = Constants.DatabaseProviders.MySql; } SaveConnectionString(connectionString, providerName); Initialize(string.Empty); @@ -240,10 +240,10 @@ namespace Umbraco.Core public string GetDatabaseConnectionString(string server, string databaseName, string user, string password, string databaseProvider, out string providerName) { - providerName = "System.Data.SqlClient"; + providerName = Constants.DatabaseProviders.SqlServer; if (databaseProvider.ToLower().Contains("mysql")) { - providerName = "MySql.Data.MySqlClient"; + providerName = Constants.DatabaseProviders.MySql; return string.Format("Server={0}; Database={1};Uid={2};Pwd={3}", server, databaseName, user, password); } if (databaseProvider.ToLower().Contains("azure")) @@ -260,7 +260,7 @@ namespace Umbraco.Core /// Name of the database public void ConfigureIntegratedSecurityDatabaseConnection(string server, string databaseName) { - const string providerName = "System.Data.SqlClient"; + const string providerName = Constants.DatabaseProviders.SqlServer; var connectionString = GetIntegratedSecurityDatabaseConnectionString(server, databaseName); SaveConnectionString(connectionString, providerName); Initialize(providerName); @@ -373,7 +373,7 @@ namespace Umbraco.Core var databaseSettings = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName]; if (databaseSettings != null && string.IsNullOrWhiteSpace(databaseSettings.ConnectionString) == false && string.IsNullOrWhiteSpace(databaseSettings.ProviderName) == false) { - var providerName = "System.Data.SqlClient"; + var providerName = Constants.DatabaseProviders.SqlServer; string connString = null; if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName)) { @@ -381,8 +381,7 @@ namespace Umbraco.Core connString = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ConnectionString; } Initialize(providerName, connString); - - DetermineSqlServerVersion(); + } else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]) == false) { @@ -395,8 +394,8 @@ namespace Umbraco.Core else if (legacyConnString.ToLowerInvariant().Contains("tcp:")) { //Must be sql azure - SaveConnectionString(legacyConnString, "System.Data.SqlClient"); - Initialize("System.Data.SqlClient"); + SaveConnectionString(legacyConnString, Constants.DatabaseProviders.SqlServer); + Initialize(Constants.DatabaseProviders.SqlServer); } else if (legacyConnString.ToLowerInvariant().Contains("datalayer=mysql")) { @@ -407,20 +406,19 @@ namespace Umbraco.Core foreach (var variable in legacyConnString.Split(';').Where(x => x.ToLowerInvariant().StartsWith("datalayer") == false)) connectionStringWithoutDatalayer = string.Format("{0}{1};", connectionStringWithoutDatalayer, variable); - SaveConnectionString(connectionStringWithoutDatalayer, "MySql.Data.MySqlClient"); - Initialize("MySql.Data.MySqlClient"); + SaveConnectionString(connectionStringWithoutDatalayer, Constants.DatabaseProviders.MySql); + Initialize(Constants.DatabaseProviders.MySql); } else { //Must be sql - SaveConnectionString(legacyConnString, "System.Data.SqlClient"); - Initialize("System.Data.SqlClient"); + SaveConnectionString(legacyConnString, Constants.DatabaseProviders.SqlServer); + Initialize(Constants.DatabaseProviders.SqlServer); } //Remove the legacy connection string, so we don't end up in a loop if something goes wrong. GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName); - - DetermineSqlServerVersion(); + } else { @@ -465,49 +463,6 @@ namespace Umbraco.Core Initialize(providerName); } - /// - /// Set the lazy resolution of determining the SQL server version if that is the db type we're using - /// - private void DetermineSqlServerVersion() - { - - var sqlServerSyntax = SqlSyntax as SqlServerSyntaxProvider; - if (sqlServerSyntax != null) - { - //this will not execute now, it is lazy so will only execute when we need to actually know - // the sql server version. - sqlServerSyntax.VersionName = new Lazy(() => - { - try - { - var database = this._factory.CreateDatabase(); - - var version = database.ExecuteScalar("SELECT SERVERPROPERTY('productversion')"); - var firstPart = version.Split('.')[0]; - switch (firstPart) - { - case "11": - return SqlServerVersionName.V2012; - case "10": - return SqlServerVersionName.V2008; - case "9": - return SqlServerVersionName.V2005; - case "8": - return SqlServerVersionName.V2000; - case "7": - return SqlServerVersionName.V7; - default: - return SqlServerVersionName.Other; - } - } - catch (Exception) - { - return SqlServerVersionName.Invalid; - } - }); - } - } - internal DatabaseSchemaResult ValidateDatabaseSchema() { if (_configured == false || (string.IsNullOrEmpty(_connectionString) || string.IsNullOrEmpty(ProviderName))) @@ -517,7 +472,7 @@ namespace Umbraco.Core { if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted - && ProviderName == "MySql.Data.MySqlClient") + && ProviderName == Constants.DatabaseProviders.MySql) { throw new InvalidOperationException("Cannot use MySql in Medium Trust configuration"); } @@ -625,13 +580,8 @@ namespace Umbraco.Core var installedSchemaVersion = new SemVersion(schemaResult.DetermineInstalledVersion()); - var installedMigrationVersion = new SemVersion(0); - //we cannot check the migrations table if it doesn't exist, this will occur when upgrading to 7.3 - if (schemaResult.ValidTables.Any(x => x.InvariantEquals("umbracoMigration"))) - { - installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService); - } - + var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService); + var targetVersion = UmbracoVersion.Current; //In some cases - like upgrading from 7.2.6 -> 7.3, there will be no migration information in the database and therefore it will @@ -731,7 +681,7 @@ namespace Umbraco.Core private Attempt CheckReadyForInstall() { if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted - && ProviderName == "MySql.Data.MySqlClient") + && ProviderName == Constants.DatabaseProviders.MySql) { throw new InvalidOperationException("Cannot use MySql in Medium Trust configuration"); } @@ -780,7 +730,7 @@ namespace Umbraco.Core { var dbIsSqlCe = false; if (databaseSettings != null && databaseSettings.ProviderName != null) - dbIsSqlCe = databaseSettings.ProviderName == "System.Data.SqlServerCe.4.0"; + dbIsSqlCe = databaseSettings.ProviderName == Constants.DatabaseProviders.SqlCe; var sqlCeDatabaseExists = false; if (dbIsSqlCe) { diff --git a/src/Umbraco.Core/DecimalExtensions.cs b/src/Umbraco.Core/DecimalExtensions.cs new file mode 100644 index 0000000000..b4d74fdb2e --- /dev/null +++ b/src/Umbraco.Core/DecimalExtensions.cs @@ -0,0 +1,23 @@ +namespace Umbraco.Core +{ + /// + /// Provides extension methods for System.Decimal. + /// + /// See System.Decimal on MSDN and also + /// http://stackoverflow.com/questions/4298719/parse-decimal-and-filter-extra-0-on-the-right/4298787#4298787. + /// + public static class DecimalExtensions + { + /// + /// Gets the normalized value. + /// + /// The value to normalize. + /// The normalized value. + /// Normalizing changes the scaling factor and removes trailing zeroes, + /// so 1.2500m comes out as 1.25m. + public static decimal Normalize(this decimal value) + { + return value / 1.000000000000000000000000000000000m; + } + } +} diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index 98fdb01ff4..2dfad2d103 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -69,7 +69,16 @@ namespace Umbraco.Core.IO } } - public static string AppPlugins + public static string AppCode + { + get + { + //NOTE: this is not configurable and shouldn't need to be + return "~/App_Code"; + } + } + + public static string AppPlugins { get { diff --git a/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs b/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs index 74a1de81f4..fcb0f183ec 100644 --- a/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs +++ b/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs @@ -1,20 +1,21 @@ -using System; using log4net.Appender; using log4net.Core; using log4net.Util; +using System; +using System.Runtime.Remoting.Messaging; namespace Umbraco.Core.Logging { - /// - /// Based on https://github.com/cjbhaines/Log4Net.Async - /// + /// + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 + /// public abstract class AsyncForwardingAppenderBase : ForwardingAppender { #region Private Members private const FixFlags DefaultFixFlags = FixFlags.Partial; - private FixFlags _fixFlags = DefaultFixFlags; - private LoggingEventHelper _loggingEventHelper; + private FixFlags fixFlags = DefaultFixFlags; + private LoggingEventHelper loggingEventHelper; #endregion Private Members @@ -22,10 +23,25 @@ namespace Umbraco.Core.Logging public FixFlags Fix { - get { return _fixFlags; } + get { return fixFlags; } set { SetFixFlags(value); } } + /// + /// Returns HttpContext.Current + /// + protected internal object HttpContext + { + get + { + return CallContext.HostContext; + } + set + { + CallContext.HostContext = value; + } + } + /// /// The logger name that will be used for logging internal errors. /// @@ -38,7 +54,7 @@ namespace Umbraco.Core.Logging public override void ActivateOptions() { base.ActivateOptions(); - _loggingEventHelper = new LoggingEventHelper(InternalLoggerName, DefaultFixFlags); + loggingEventHelper = new LoggingEventHelper(InternalLoggerName, DefaultFixFlags); InitializeAppenders(); } @@ -52,10 +68,10 @@ namespace Umbraco.Core.Logging private void SetFixFlags(FixFlags newFixFlags) { - if (newFixFlags != _fixFlags) + if (newFixFlags != fixFlags) { - _loggingEventHelper.Fix = newFixFlags; - _fixFlags = newFixFlags; + loggingEventHelper.Fix = newFixFlags; + fixFlags = newFixFlags; InitializeAppenders(); } } @@ -84,7 +100,7 @@ namespace Umbraco.Core.Logging protected void ForwardInternalError(string message, Exception exception, Type thisType) { LogLog.Error(thisType, message, exception); - var loggingEvent = _loggingEventHelper.CreateLoggingEvent(Level.Error, message, exception); + var loggingEvent = loggingEventHelper.CreateLoggingEvent(Level.Error, message, exception); ForwardLoggingEvent(loggingEvent, thisType); } diff --git a/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs b/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs index cb58ebbfaa..c226bd03c8 100644 --- a/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs +++ b/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs @@ -1,6 +1,7 @@ using log4net.Core; using log4net.Util; using System; +using System.ComponentModel; using System.Runtime.Remoting.Messaging; using System.Security.Principal; using System.Threading; @@ -12,7 +13,11 @@ namespace Umbraco.Core.Logging /// /// Based on https://github.com/cjbhaines/Log4Net.Async /// which is based on code by Chris Haines http://cjbhaines.wordpress.com/2012/02/13/asynchronous-log4net-appenders/ + /// This is an old/deprecated logger and has been superceded by ParallelForwardingAppender which is included in Umbraco and + /// also by AsyncForwardingAppender in the Log4Net.Async library. /// + [Obsolete("This is superceded by the ParallelForwardingAppender, this will be removed in v8")] + [EditorBrowsable(EditorBrowsableState.Never)] public class AsynchronousRollingFileAppender : RollingFileAppender { private RingBuffer pendingAppends; @@ -198,79 +203,4 @@ namespace Umbraco.Core.Logging } } - internal interface IQueue - { - void Enqueue(T item); - bool TryDequeue(out T ret); - } - - internal class RingBuffer : IQueue - { - private readonly object lockObject = new object(); - private readonly T[] buffer; - private readonly int size; - private int readIndex = 0; - private int writeIndex = 0; - private bool bufferFull = false; - - public int Size { get { return size; } } - - public event Action BufferOverflow; - - public RingBuffer(int size) - { - this.size = size; - buffer = new T[size]; - } - - public void Enqueue(T item) - { - var bufferWasFull = false; - lock (lockObject) - { - buffer[writeIndex] = item; - writeIndex = (++writeIndex) % size; - if (bufferFull) - { - bufferWasFull = true; - readIndex = writeIndex; - } - else if (writeIndex == readIndex) - { - bufferFull = true; - } - } - - if (bufferWasFull) - { - if (BufferOverflow != null) - { - BufferOverflow(this, EventArgs.Empty); - } - } - } - - public bool TryDequeue(out T ret) - { - if (readIndex == writeIndex && !bufferFull) - { - ret = default(T); - return false; - } - lock (lockObject) - { - if (readIndex == writeIndex && !bufferFull) - { - ret = default(T); - return false; - } - - ret = buffer[readIndex]; - buffer[readIndex] = default(T); - readIndex = (++readIndex) % size; - bufferFull = false; - return true; - } - } - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/IQueue.cs b/src/Umbraco.Core/Logging/IQueue.cs new file mode 100644 index 0000000000..d063993ef6 --- /dev/null +++ b/src/Umbraco.Core/Logging/IQueue.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Logging +{ + /// + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 + /// + /// + internal interface IQueue + { + void Enqueue(T item); + bool TryDequeue(out T ret); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/ImageProcessorLogger.cs b/src/Umbraco.Core/Logging/ImageProcessorLogger.cs new file mode 100644 index 0000000000..02ca9c2949 --- /dev/null +++ b/src/Umbraco.Core/Logging/ImageProcessorLogger.cs @@ -0,0 +1,46 @@ +namespace Umbraco.Core.Logging +{ + using System; + using System.Runtime.CompilerServices; + + using ImageProcessor.Common.Exceptions; + + /// + /// A logger for explicitly logging ImageProcessor exceptions. + /// + /// Creating this logger is enough for ImageProcessor to find and replace its in-built debug logger + /// without any additional configuration required. This class currently has to be public in order + /// to do so. + /// + /// + public sealed class ImageProcessorLogger : ImageProcessor.Common.Exceptions.ILogger + { + /// + /// Logs the specified message as an error. + /// + /// The type calling the logger. + /// The message to log. + /// The property or method name calling the log. + /// The line number where the method is called. + public void Log(string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0) + { + // Using LogHelper since the ImageProcessor logger expects a parameterless constructor. + var message = string.Format("{0} {1} : {2}", callerName, lineNumber, text); + LogHelper.Error(string.Empty, new ImageProcessingException(message)); + } + + /// + /// Logs the specified message as an error. + /// + /// The type calling the logger. + /// The message to log. + /// The property or method name calling the log. + /// The line number where the method is called. + public void Log(Type type, string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0) + { + // Using LogHelper since the ImageProcessor logger expects a parameterless constructor. + var message = string.Format("{0} {1} : {2}", callerName, lineNumber, text); + LogHelper.Error(type, string.Empty, new ImageProcessingException(message)); + } + } +} diff --git a/src/Umbraco.Core/Logging/LoggingEventContext.cs b/src/Umbraco.Core/Logging/LoggingEventContext.cs index 159af4266b..88222e3c05 100644 --- a/src/Umbraco.Core/Logging/LoggingEventContext.cs +++ b/src/Umbraco.Core/Logging/LoggingEventContext.cs @@ -3,15 +3,18 @@ using log4net.Core; namespace Umbraco.Core.Logging { /// - /// Based on https://github.com/cjbhaines/Log4Net.Async + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 /// - internal class LoggingEventContext + internal sealed class LoggingEventContext { - public LoggingEventContext(LoggingEvent loggingEvent) + public LoggingEventContext(LoggingEvent loggingEvent, object httpContext) { LoggingEvent = loggingEvent; + HttpContext = httpContext; } public LoggingEvent LoggingEvent { get; set; } + + public object HttpContext { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/LoggingEventHelper.cs b/src/Umbraco.Core/Logging/LoggingEventHelper.cs index c788e115f2..129098279a 100644 --- a/src/Umbraco.Core/Logging/LoggingEventHelper.cs +++ b/src/Umbraco.Core/Logging/LoggingEventHelper.cs @@ -4,9 +4,9 @@ using log4net.Core; namespace Umbraco.Core.Logging { /// - /// Based on https://github.com/cjbhaines/Log4Net.Async + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 /// - internal class LoggingEventHelper + internal sealed class LoggingEventHelper { // needs to be a seperate class so that location is determined correctly by log4net when required @@ -23,8 +23,10 @@ namespace Umbraco.Core.Logging public LoggingEvent CreateLoggingEvent(Level level, string message, Exception exception) { - var loggingEvent = new LoggingEvent(HelperType, null, loggerName, level, message, exception); - loggingEvent.Fix = Fix; + var loggingEvent = new LoggingEvent(HelperType, null, loggerName, level, message, exception) + { + Fix = Fix + }; return loggingEvent; } } diff --git a/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs b/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs index cf7efdb4c4..48bb3ec710 100644 --- a/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs +++ b/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs @@ -1,9 +1,9 @@ +using log4net.Core; +using log4net.Util; using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; -using log4net.Core; -using log4net.Util; namespace Umbraco.Core.Logging { @@ -11,7 +11,7 @@ namespace Umbraco.Core.Logging /// An asynchronous appender based on /// /// - /// Based on https://github.com/cjbhaines/Log4Net.Async + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 /// public class ParallelForwardingAppender : AsyncForwardingAppenderBase, IDisposable { @@ -22,11 +22,11 @@ namespace Umbraco.Core.Logging private CancellationTokenSource _loggingCancelationTokenSource; private CancellationToken _loggingCancelationToken; private Task _loggingTask; - private Double _shutdownFlushTimeout = 1; - private TimeSpan _shutdownFlushTimespan = TimeSpan.FromSeconds(1); + private Double _shutdownFlushTimeout = 2; + private TimeSpan _shutdownFlushTimespan = TimeSpan.FromSeconds(2); private static readonly Type ThisType = typeof(ParallelForwardingAppender); - private volatile bool _shutDownRequested; - private int? _bufferSize = DefaultBufferSize; + private volatile bool shutDownRequested; + private int? bufferSize = DefaultBufferSize; #endregion Private Members @@ -37,8 +37,8 @@ namespace Umbraco.Core.Logging /// public override int? BufferSize { - get { return _bufferSize; } - set { _bufferSize = value; } + get { return bufferSize; } + set { bufferSize = value; } } public int BufferEntryCount @@ -67,7 +67,12 @@ namespace Umbraco.Core.Logging protected override string InternalLoggerName { - get { return "ParallelForwardingAppender"; } + get + { + { + return "ParallelForwardingAppender"; + } + } } #endregion Properties @@ -83,7 +88,7 @@ namespace Umbraco.Core.Logging private void StartForwarding() { - if (_shutDownRequested) + if (shutDownRequested) { return; } @@ -111,7 +116,7 @@ namespace Umbraco.Core.Logging private void CompleteSubscriberTask() { - _shutDownRequested = true; + shutDownRequested = true; if (_loggingEvents == null || _loggingEvents.IsAddingCompleted) { return; @@ -154,7 +159,7 @@ namespace Umbraco.Core.Logging loggingEvent.Fix = Fix; //In the case where blocking on a full collection, and the task is subsequently completed, the cancellation token //will prevent the entry from attempting to add to the completed collection which would result in an exception. - _loggingEvents.Add(new LoggingEventContext(loggingEvent), _loggingCancelationToken); + _loggingEvents.Add(new LoggingEventContext(loggingEvent, HttpContext), _loggingCancelationToken); } protected override void Append(LoggingEvent[] loggingEvents) @@ -187,6 +192,7 @@ namespace Umbraco.Core.Logging //This call blocks until an item is available or until adding is completed foreach (var entry in _loggingEvents.GetConsumingEnumerable(_loggingCancelationToken)) { + HttpContext = entry.HttpContext; ForwardLoggingEvent(entry.LoggingEvent, ThisType); } } diff --git a/src/Umbraco.Core/Logging/RingBuffer.cs b/src/Umbraco.Core/Logging/RingBuffer.cs new file mode 100644 index 0000000000..8dd78129b8 --- /dev/null +++ b/src/Umbraco.Core/Logging/RingBuffer.cs @@ -0,0 +1,78 @@ +using System; + +namespace Umbraco.Core.Logging +{ + /// + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 + /// + /// + internal sealed class RingBuffer : IQueue + { + private readonly object lockObject = new object(); + private readonly T[] buffer; + private readonly int size; + private int readIndex = 0; + private int writeIndex = 0; + private bool bufferFull = false; + + public int Size { get { return size; } } + + public event Action BufferOverflow; + + public RingBuffer(int size) + { + this.size = size; + buffer = new T[size]; + } + + public void Enqueue(T item) + { + var bufferWasFull = false; + lock (lockObject) + { + buffer[writeIndex] = item; + writeIndex = (++writeIndex) % size; + if (bufferFull) + { + bufferWasFull = true; + readIndex = writeIndex; + } + else if (writeIndex == readIndex) + { + bufferFull = true; + } + } + + if (bufferWasFull) + { + if (BufferOverflow != null) + { + BufferOverflow(this, EventArgs.Empty); + } + } + } + + public bool TryDequeue(out T ret) + { + if (readIndex == writeIndex && !bufferFull) + { + ret = default(T); + return false; + } + lock (lockObject) + { + if (readIndex == writeIndex && !bufferFull) + { + ret = default(T); + return false; + } + + ret = buffer[readIndex]; + buffer[readIndex] = default(T); + readIndex = (++readIndex) % size; + bufferFull = false; + return true; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index b8e96c2793..ec73e1ff5e 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -75,14 +76,19 @@ namespace Umbraco.Core.Models _contentType = contentType; } - private static readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.Template); - private static readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.Published); - private static readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); - private static readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ReleaseDate); - private static readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ExpireDate); - private static readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.WriterId); - private static readonly PropertyInfo NodeNameSelector = ExpressionHelper.GetPropertyInfo(x => x.NodeName); - private static readonly PropertyInfo PermissionsChangedSelector = ExpressionHelper.GetPropertyInfo(x => x.PermissionsChanged); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.Template); + public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.Published); + public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); + public readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ReleaseDate); + public readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ExpireDate); + public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.WriterId); + public readonly PropertyInfo NodeNameSelector = ExpressionHelper.GetPropertyInfo(x => x.NodeName); + public readonly PropertyInfo PermissionsChangedSelector = ExpressionHelper.GetPropertyInfo(x => x.PermissionsChanged); + } /// /// Gets or sets the template used by the Content. @@ -96,14 +102,7 @@ namespace Umbraco.Core.Models public virtual ITemplate Template { get { return _template; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _template = value; - return _template; - }, _template, TemplateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _template, Ps.Value.TemplateSelector); } } /// @@ -141,31 +140,18 @@ namespace Umbraco.Core.Models public bool Published { get { return _published; } - internal set - { - SetPropertyValueAndDetectChanges(o => - { - _published = value; - return _published; - }, _published, PublishedSelector); - } + internal set { SetPropertyValueAndDetectChanges(value, ref _published, Ps.Value.PublishedSelector); } } /// /// Language of the data contained within this Content object. /// [Obsolete("This is not used and will be removed from the codebase in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] public string Language { get { return _language; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _language = value; - return _language; - }, _language, LanguageSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); } } /// @@ -175,14 +161,7 @@ namespace Umbraco.Core.Models public DateTime? ReleaseDate { get { return _releaseDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _releaseDate = value; - return _releaseDate; - }, _releaseDate, ReleaseDateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _releaseDate, Ps.Value.ReleaseDateSelector); } } /// @@ -192,14 +171,7 @@ namespace Umbraco.Core.Models public DateTime? ExpireDate { get { return _expireDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _expireDate = value; - return _expireDate; - }, _expireDate, ExpireDateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _expireDate, Ps.Value.ExpireDateSelector); } } /// @@ -209,14 +181,7 @@ namespace Umbraco.Core.Models public virtual int WriterId { get { return _writer; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _writer = value; - return _writer; - }, _writer, WriterSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _writer, Ps.Value.WriterSelector); } } /// @@ -229,14 +194,7 @@ namespace Umbraco.Core.Models internal string NodeName { get { return _nodeName; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _nodeName = value; - return _nodeName; - }, _nodeName, NodeNameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _nodeName, Ps.Value.NodeNameSelector); } } /// @@ -246,14 +204,7 @@ namespace Umbraco.Core.Models internal bool PermissionsChanged { get { return _permissionsChanged; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _permissionsChanged = value; - return _permissionsChanged; - }, _permissionsChanged, PermissionsChangedSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _permissionsChanged, Ps.Value.PermissionsChangedSelector); } } /// diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index b3d0f693d9..0fc3bac044 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -81,19 +81,24 @@ namespace Umbraco.Core.Models _additionalData = new Dictionary(); } - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - private static readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); - private readonly static PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); + public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + public readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); + public readonly PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); + } protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(PropertyCollectionSelector); + OnPropertyChanged(Ps.Value.PropertyCollectionSelector); } /// @@ -114,7 +119,7 @@ namespace Umbraco.Core.Models set { _parentId = new Lazy(() => value); - OnPropertyChanged(ParentIdSelector); + OnPropertyChanged(Ps.Value.ParentIdSelector); } } @@ -125,14 +130,7 @@ namespace Umbraco.Core.Models public virtual string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -142,14 +140,7 @@ namespace Umbraco.Core.Models public virtual int SortOrder { get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } /// @@ -159,14 +150,7 @@ namespace Umbraco.Core.Models public virtual int Level { get { return _level; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _level = value; - return _level; - }, _level, LevelSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } } /// @@ -176,14 +160,7 @@ namespace Umbraco.Core.Models public virtual string Path //Setting this value should be handled by the class not the user { get { return _path; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _path = value; - return _path; - }, _path, PathSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } } /// @@ -193,14 +170,7 @@ namespace Umbraco.Core.Models public virtual int CreatorId { get { return _creatorId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _creatorId = value; - return _creatorId; - }, _creatorId, CreatorIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); } } /// @@ -212,14 +182,7 @@ namespace Umbraco.Core.Models public virtual bool Trashed //Setting this value should be handled by the class not the user { get { return _trashed; } - internal set - { - SetPropertyValueAndDetectChanges(o => - { - _trashed = value; - return _trashed; - }, _trashed, TrashedSelector); - } + internal set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); } } /// @@ -244,14 +207,7 @@ namespace Umbraco.Core.Models } return _contentTypeId; } - protected set - { - SetPropertyValueAndDetectChanges(o => - { - _contentTypeId = value; - return _contentTypeId; - }, _contentTypeId, DefaultContentTypeIdSelector); - } + protected set { SetPropertyValueAndDetectChanges(value, ref _contentTypeId, Ps.Value.DefaultContentTypeIdSelector); } } /// diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 0926e48e31..88c498a147 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -48,8 +48,13 @@ namespace Umbraco.Core.Models _allowedTemplates = new List(); } - private static readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultTemplateId); - private static readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedTemplates); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultTemplateId); + public readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedTemplates); + } /// /// Gets or sets the alias of the default Template. @@ -70,14 +75,7 @@ namespace Umbraco.Core.Models internal int DefaultTemplateId { get { return _defaultTemplate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _defaultTemplate = value; - return _defaultTemplate; - }, _defaultTemplate, DefaultTemplateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _defaultTemplate, Ps.Value.DefaultTemplateSelector); } } /// @@ -92,11 +90,7 @@ namespace Umbraco.Core.Models get { return _allowedTemplates; } set { - SetPropertyValueAndDetectChanges(o => - { - _allowedTemplates = value; - return _allowedTemplates; - }, _allowedTemplates, AllowedTemplatesSelector, + SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector, //Custom comparer for enumerable new DelegateEqualityComparer>( (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable), diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index a0305d2cfb..2982713e5a 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -67,33 +67,38 @@ namespace Umbraco.Core.Models _additionalData = new Dictionary(); } - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo(x => x.Description); - private static readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); - private static readonly PropertyInfo ThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.Thumbnail); - private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - private static readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo(x => x.AllowedAsRoot); - private static readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo(x => x.IsContainer); - private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - private static readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedContentTypes); - private static readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyGroups); - private static readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyTypes); - private static readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPropertyTypeBeenRemoved); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); + public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo(x => x.Description); + public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); + public readonly PropertyInfo ThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.Thumbnail); + public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + public readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo(x => x.AllowedAsRoot); + public readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo(x => x.IsContainer); + public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + public readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedContentTypes); + public readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyGroups); + public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyTypes); + public readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPropertyTypeBeenRemoved); + } protected void PropertyGroupsChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(PropertyGroupCollectionSelector); + OnPropertyChanged(Ps.Value.PropertyGroupCollectionSelector); } protected void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(PropertyTypeCollectionSelector); + OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector); } /// @@ -115,7 +120,7 @@ namespace Umbraco.Core.Models set { _parentId = new Lazy(() => value); - OnPropertyChanged(ParentIdSelector); + OnPropertyChanged(Ps.Value.ParentIdSelector); } } @@ -126,14 +131,7 @@ namespace Umbraco.Core.Models public virtual string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -143,14 +141,7 @@ namespace Umbraco.Core.Models public virtual int Level //NOTE Is this relevant for a ContentType? { get { return _level; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _level = value; - return _level; - }, _level, LevelSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } } /// @@ -160,14 +151,7 @@ namespace Umbraco.Core.Models public virtual string Path //NOTE Is this relevant for a ContentType? { get { return _path; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _path = value; - return _path; - }, _path, PathSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } } /// @@ -179,12 +163,10 @@ namespace Umbraco.Core.Models get { return _alias; } set { - SetPropertyValueAndDetectChanges(o => - { - //_alias = value.ToSafeAlias(); - _alias = value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase); - return _alias; - }, _alias, AliasSelector); + SetPropertyValueAndDetectChanges( + value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), + ref _alias, + Ps.Value.AliasSelector); } } @@ -195,14 +177,7 @@ namespace Umbraco.Core.Models public virtual string Description { get { return _description; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _description = value; - return _description; - }, _description, DescriptionSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _description, Ps.Value.DescriptionSelector); } } /// @@ -212,14 +187,7 @@ namespace Umbraco.Core.Models public virtual int SortOrder { get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } /// @@ -229,14 +197,7 @@ namespace Umbraco.Core.Models public virtual string Icon { get { return _icon; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _icon = value; - return _icon; - }, _icon, IconSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _icon, Ps.Value.IconSelector); } } /// @@ -246,14 +207,7 @@ namespace Umbraco.Core.Models public virtual string Thumbnail { get { return _thumbnail; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _thumbnail = value; - return _thumbnail; - }, _thumbnail, ThumbnailSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _thumbnail, Ps.Value.ThumbnailSelector); } } /// @@ -263,14 +217,7 @@ namespace Umbraco.Core.Models public virtual int CreatorId { get { return _creatorId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _creatorId = value; - return _creatorId; - }, _creatorId, CreatorIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); } } /// @@ -280,14 +227,7 @@ namespace Umbraco.Core.Models public virtual bool AllowedAsRoot { get { return _allowedAsRoot; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _allowedAsRoot = value; - return _allowedAsRoot; - }, _allowedAsRoot, AllowedAsRootSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _allowedAsRoot, Ps.Value.AllowedAsRootSelector); } } /// @@ -300,14 +240,7 @@ namespace Umbraco.Core.Models public virtual bool IsContainer { get { return _isContainer; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _isContainer = value; - return _isContainer; - }, _isContainer, IsContainerSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _isContainer, Ps.Value.IsContainerSelector); } } /// @@ -318,14 +251,7 @@ namespace Umbraco.Core.Models public virtual bool Trashed //NOTE Is this relevant for a ContentType? { get { return _trashed; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _trashed = value; - return _trashed; - }, _trashed, TrashedSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); } } private IDictionary _additionalData; @@ -347,15 +273,11 @@ namespace Umbraco.Core.Models get { return _allowedContentTypes; } set { - SetPropertyValueAndDetectChanges(o => - { - _allowedContentTypes = value; - return _allowedContentTypes; - }, _allowedContentTypes, AllowedContentTypesSelector, + SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, Ps.Value.AllowedContentTypesSelector, //Custom comparer for enumerable new DelegateEqualityComparer>( (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable), - sorts => sorts.GetHashCode())); + sorts => sorts.GetHashCode())); } } @@ -418,7 +340,7 @@ namespace Umbraco.Core.Models private set { _hasPropertyTypeBeenRemoved = value; - OnPropertyChanged(HasPropertyTypeBeenRemovedSelector); + OnPropertyChanged(Ps.Value.HasPropertyTypeBeenRemovedSelector); } } @@ -543,7 +465,7 @@ namespace Umbraco.Core.Models // actually remove the group PropertyGroups.RemoveItem(propertyGroupName); - OnPropertyChanged(PropertyGroupCollectionSelector); + OnPropertyChanged(Ps.Value.PropertyGroupCollectionSelector); } /// diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 4cf4a08bf1..e53011cc51 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -32,9 +32,13 @@ namespace Umbraco.Core.Models AddContentType(parent); } - private static readonly PropertyInfo ContentTypeCompositionSelector = - ExpressionHelper.GetPropertyInfo>( - x => x.ContentTypeComposition); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ContentTypeCompositionSelector = + ExpressionHelper.GetPropertyInfo>(x => x.ContentTypeComposition); + } /// /// Gets or sets the content types that compose this content type. @@ -46,7 +50,7 @@ namespace Umbraco.Core.Models set { _contentTypeComposition = value.ToList(); - OnPropertyChanged(ContentTypeCompositionSelector); + OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); } } @@ -102,7 +106,7 @@ namespace Umbraco.Core.Models throw new InvalidCompositionException(Alias, contentType.Alias, conflictingPropertyTypeAliases.ToArray()); _contentTypeComposition.Add(contentType); - OnPropertyChanged(ContentTypeCompositionSelector); + OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); return true; } return false; @@ -128,7 +132,7 @@ namespace Umbraco.Core.Models if (compositionIdsToRemove.Any()) RemovedContentTypeKeyTracker.AddRange(compositionIdsToRemove); - OnPropertyChanged(ContentTypeCompositionSelector); + OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector); return _contentTypeComposition.Remove(contentTypeComposition); } return false; diff --git a/src/Umbraco.Core/Models/DataTypeDatabaseType.cs b/src/Umbraco.Core/Models/DataTypeDatabaseType.cs index 1db8ac65cb..2fccdc0645 100644 --- a/src/Umbraco.Core/Models/DataTypeDatabaseType.cs +++ b/src/Umbraco.Core/Models/DataTypeDatabaseType.cs @@ -6,10 +6,6 @@ namespace Umbraco.Core.Models /// /// Enum of the various DbTypes for which the Property values are stored /// - /// - /// Object is added to support complex values from PropertyEditors, - /// but will be saved under the Ntext column. - /// [Serializable] [DataContract] public enum DataTypeDatabaseType diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs index 4c12d6fbef..3ba5125a90 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs @@ -65,15 +65,20 @@ namespace Umbraco.Core.Models _additionalData = new Dictionary(); } - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - private static readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - private static readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); - private static readonly PropertyInfo DatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DatabaseType); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); + public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + public readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); + public readonly PropertyInfo DatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DatabaseType); + } /// /// Gets or sets the Id of the Parent entity @@ -83,14 +88,7 @@ namespace Umbraco.Core.Models public int ParentId { get { return _parentId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _parentId = value; - return _parentId; - }, _parentId, ParentIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } } /// @@ -100,14 +98,7 @@ namespace Umbraco.Core.Models public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -117,14 +108,7 @@ namespace Umbraco.Core.Models public int SortOrder { get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } /// @@ -134,14 +118,7 @@ namespace Umbraco.Core.Models public int Level { get { return _level; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _level = value; - return _level; - }, _level, LevelSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } } /// @@ -151,14 +128,7 @@ namespace Umbraco.Core.Models public string Path //Setting this value should be handled by the class not the user { get { return _path; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _path = value; - return _path; - }, _path, PathSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } } /// @@ -168,14 +138,7 @@ namespace Umbraco.Core.Models public int CreatorId { get { return _creatorId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _creatorId = value; - return _creatorId; - }, _creatorId, UserIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.UserIdSelector); } } //NOTE: SD: Why do we have this ?? @@ -189,11 +152,7 @@ namespace Umbraco.Core.Models get { return _trashed; } internal set { - SetPropertyValueAndDetectChanges(o => - { - _trashed = value; - return _trashed; - }, _trashed, TrashedSelector); + SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data _additionalData["Trashed"] = value; } @@ -205,11 +164,7 @@ namespace Umbraco.Core.Models get { return _propertyEditorAlias; } set { - SetPropertyValueAndDetectChanges(o => - { - _propertyEditorAlias = value; - return _propertyEditorAlias; - }, _propertyEditorAlias, PropertyEditorAliasSelector); + SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data _additionalData["DatabaseType"] = value; } @@ -245,12 +200,7 @@ namespace Umbraco.Core.Models get { return _databaseType; } set { - SetPropertyValueAndDetectChanges(o => - { - _databaseType = value; - return _databaseType; - }, _databaseType, DatabaseTypeSelector); - + SetPropertyValueAndDetectChanges(value, ref _databaseType, Ps.Value.DatabaseTypeSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data _additionalData["DatabaseType"] = value; } diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index 749c629d19..42b047e35b 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -30,9 +30,14 @@ namespace Umbraco.Core.Models _translations = new List(); } - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - private static readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ItemKey); - private static readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Translations); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ItemKey); + public readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Translations); + } /// /// Gets or Sets the Parent Id of the Dictionary Item @@ -41,14 +46,7 @@ namespace Umbraco.Core.Models public Guid? ParentId { get { return _parentId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _parentId = value; - return _parentId; - }, _parentId, ParentIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } } /// @@ -58,14 +56,7 @@ namespace Umbraco.Core.Models public string ItemKey { get { return _itemKey; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _itemKey = value; - return _itemKey; - }, _itemKey, ItemKeySelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _itemKey, Ps.Value.ItemKeySelector); } } /// @@ -77,25 +68,21 @@ namespace Umbraco.Core.Models get { return _translations; } set { - SetPropertyValueAndDetectChanges(o => + var asArray = value.ToArray(); + //ensure the language callback is set on each translation + if (GetLanguage != null) { - var asArray = value.ToArray(); - //ensure the language callback is set on each translation - if (GetLanguage != null) + foreach (var translation in asArray.OfType()) { - foreach (var translation in asArray.OfType()) - { - translation.GetLanguage = GetLanguage; - } + translation.GetLanguage = GetLanguage; } + } - _translations = asArray; - return _translations; - }, _translations, TranslationsSelector, + SetPropertyValueAndDetectChanges(asArray, ref _translations, Ps.Value.TranslationsSelector, //Custom comparer for enumerable new DelegateEqualityComparer>( (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations), - enumerable => enumerable.GetHashCode())); + enumerable => enumerable.GetHashCode())); } } } diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index 59f96dbe85..4e5b1c2437 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Models private string _value; //note: this will be memberwise cloned private int _languageId; - + public DictionaryTranslation(ILanguage language, string value) { if (language == null) throw new ArgumentNullException("language"); @@ -50,8 +50,13 @@ namespace Umbraco.Core.Models Key = uniqueId; } - private static readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); - private static readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); + public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); + } /// /// Gets or sets the for the translation @@ -79,12 +84,8 @@ namespace Umbraco.Core.Models } set { - SetPropertyValueAndDetectChanges(o => - { - _language = value; - _languageId = _language == null ? -1 : _language.Id; - return _language; - }, _language, LanguageSelector); + SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); + _languageId = _language == null ? -1 : _language.Id; } } @@ -100,14 +101,7 @@ namespace Umbraco.Core.Models public string Value { get { return _value; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _value = value; - return _value; - }, _value, ValueSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); } } public override object DeepClone() diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs index c4838dfd0a..637255a1c8 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -23,13 +23,17 @@ namespace Umbraco.Core.Models.EntityBase private DateTime _updateDate; private bool _wasCancelled; - private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); - private static readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); - private static readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDate); - private static readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.UpdateDate); - private static readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.HasIdentity); - private static readonly PropertyInfo WasCancelledSelector = ExpressionHelper.GetPropertyInfo(x => x.WasCancelled); - + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); + public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); + public readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDate); + public readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.UpdateDate); + public readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.HasIdentity); + public readonly PropertyInfo WasCancelledSelector = ExpressionHelper.GetPropertyInfo(x => x.WasCancelled); + } /// /// Integer Id @@ -37,18 +41,11 @@ namespace Umbraco.Core.Models.EntityBase [DataMember] public int Id { - get - { - return _id; - } + get { return _id; } set { - SetPropertyValueAndDetectChanges(o => - { - _id = value; - HasIdentity = true; //set the has Identity - return _id; - }, _id, IdSelector); + SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector); + HasIdentity = true; //set the has Identity } } @@ -67,14 +64,7 @@ namespace Umbraco.Core.Models.EntityBase _key = Guid.NewGuid(); return _key; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _key = value; - return _key; - }, _key, KeySelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); } } /// @@ -84,14 +74,7 @@ namespace Umbraco.Core.Models.EntityBase public DateTime CreateDate { get { return _createDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _createDate = value; - return _createDate; - }, _createDate, CreateDateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _createDate, Ps.Value.CreateDateSelector); } } /// @@ -105,14 +88,7 @@ namespace Umbraco.Core.Models.EntityBase internal bool WasCancelled { get { return _wasCancelled; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _wasCancelled = value; - return _wasCancelled; - }, _wasCancelled, WasCancelledSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _wasCancelled, Ps.Value.WasCancelledSelector); } } /// @@ -122,14 +98,7 @@ namespace Umbraco.Core.Models.EntityBase public DateTime UpdateDate { get { return _updateDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _updateDate = value; - return _updateDate; - }, _updateDate, UpdateDateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _updateDate, Ps.Value.UpdateDateSelector); } } internal virtual void ResetIdentity() @@ -166,14 +135,7 @@ namespace Umbraco.Core.Models.EntityBase { return _hasIdentity; } - protected set - { - SetPropertyValueAndDetectChanges(o => - { - _hasIdentity = value; - return _hasIdentity; - }, _hasIdentity, HasIdentitySelector); - } + protected set { SetPropertyValueAndDetectChanges(value, ref _hasIdentity, Ps.Value.HasIdentitySelector); } } //TODO: Make this NOT virtual or even exist really! diff --git a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs index 0d5378d253..407237ad60 100644 --- a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs +++ b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs @@ -18,7 +18,9 @@ namespace Umbraco.Core.Models.EntityBase //TODO: This needs to go on to ICanBeDirty http://issues.umbraco.org/issue/U4-5662 public virtual IEnumerable GetDirtyProperties() { - return _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key); + return _propertyChangedInfo == null + ? Enumerable.Empty() + : _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key); } private bool _changeTrackingEnabled = true; @@ -26,12 +28,12 @@ namespace Umbraco.Core.Models.EntityBase /// /// Tracks the properties that have changed /// - private IDictionary _propertyChangedInfo = new Dictionary(); + private IDictionary _propertyChangedInfo; /// /// Tracks the properties that we're changed before the last commit (or last call to ResetDirtyProperties) /// - private IDictionary _lastPropertyChangedInfo = null; + private IDictionary _lastPropertyChangedInfo; /// /// Property changed event @@ -47,12 +49,13 @@ namespace Umbraco.Core.Models.EntityBase //return if we're not tracking changes if (_changeTrackingEnabled == false) return; + if (_propertyChangedInfo == null) + _propertyChangedInfo = new Dictionary(); + _propertyChangedInfo[propertyInfo.Name] = true; if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyInfo.Name)); - } + PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyInfo.Name)); } /// @@ -62,7 +65,7 @@ namespace Umbraco.Core.Models.EntityBase /// True if Property is dirty, otherwise False public virtual bool IsPropertyDirty(string propertyName) { - return _propertyChangedInfo.Any(x => x.Key == propertyName); + return _propertyChangedInfo != null && _propertyChangedInfo.Any(x => x.Key == propertyName); } /// @@ -71,7 +74,7 @@ namespace Umbraco.Core.Models.EntityBase /// True if entity is dirty, otherwise False public virtual bool IsDirty() { - return _propertyChangedInfo.Any(); + return _propertyChangedInfo != null && _propertyChangedInfo.Any(); } /// @@ -90,7 +93,7 @@ namespace Umbraco.Core.Models.EntityBase /// True if Property was changed, otherwise False. Returns false if the entity had not been previously changed. public virtual bool WasPropertyDirty(string propertyName) { - return WasDirty() && _lastPropertyChangedInfo.Any(x => x.Key == propertyName); + return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any(x => x.Key == propertyName); } /// @@ -100,7 +103,7 @@ namespace Umbraco.Core.Models.EntityBase { //NOTE: We cannot .Clear() because when we memberwise clone this will be the SAME // instance as the one on the clone, so we need to create a new instance. - _lastPropertyChangedInfo = new Dictionary(); + _lastPropertyChangedInfo = null; } /// @@ -130,26 +133,29 @@ namespace Umbraco.Core.Models.EntityBase if (rememberPreviouslyChangedProperties) { //copy the changed properties to the last changed properties - _lastPropertyChangedInfo = _propertyChangedInfo.ToDictionary(v => v.Key, v => v.Value); + if (_propertyChangedInfo != null) + { + _lastPropertyChangedInfo = _propertyChangedInfo.ToDictionary(v => v.Key, v => v.Value); + } } //NOTE: We cannot .Clear() because when we memberwise clone this will be the SAME // instance as the one on the clone, so we need to create a new instance. - _propertyChangedInfo = new Dictionary(); + _propertyChangedInfo = null; } - protected void ResetChangeTrackingCollections() + public void ResetChangeTrackingCollections() { - _propertyChangedInfo = new Dictionary(); - _lastPropertyChangedInfo = new Dictionary(); + _propertyChangedInfo = null; + _lastPropertyChangedInfo = null; } - protected void DisableChangeTracking() + public void DisableChangeTracking() { _changeTrackingEnabled = false; } - protected void EnableChangeTracking() + public void EnableChangeTracking() { _changeTrackingEnabled = true; } @@ -158,60 +164,61 @@ namespace Umbraco.Core.Models.EntityBase /// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did /// it will ensure that the property has a dirty flag set. /// - /// - /// + /// + /// /// /// returns true if the value changed /// - /// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we - /// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set + /// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we + /// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set /// to the same value, so it's really not dirty. /// - internal bool SetPropertyValueAndDetectChanges(Func setValue, T value, PropertyInfo propertySelector) + internal void SetPropertyValueAndDetectChanges(T newVal, ref T origVal, PropertyInfo propertySelector) { if ((typeof(T) == typeof(string) == false) && TypeHelper.IsTypeAssignableFrom(typeof(T))) { throw new InvalidOperationException("This method does not support IEnumerable instances. For IEnumerable instances a manual custom equality check will be required"); } - return SetPropertyValueAndDetectChanges(setValue, value, propertySelector, - new DelegateEqualityComparer( - //Standard Equals comparison - (arg1, arg2) => Equals(arg1, arg2), - arg => arg.GetHashCode())); - + SetPropertyValueAndDetectChanges(newVal, ref origVal, propertySelector, EqualityComparer.Default); } /// /// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did /// it will ensure that the property has a dirty flag set. /// - /// - /// + /// + /// /// /// The equality comparer to use /// returns true if the value changed /// - /// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we - /// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set + /// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we + /// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set /// to the same value, so it's really not dirty. /// - internal bool SetPropertyValueAndDetectChanges(Func setValue, T value, PropertyInfo propertySelector, IEqualityComparer comparer) + internal void SetPropertyValueAndDetectChanges(T newVal, ref T origVal, PropertyInfo propertySelector, IEqualityComparer comparer) { - var initVal = value; - var newVal = setValue(value); - - //don't track changes, just set the value (above) - if (_changeTrackingEnabled == false) return false; - - if (comparer.Equals(initVal, newVal) == false) + //don't track changes, just set the value + if (_changeTrackingEnabled == false) { - OnPropertyChanged(propertySelector); - return true; + //set the original value + origVal = newVal; + } + else + { + //check changed + var changed = comparer.Equals(origVal, newVal) == false; + + //set the original value + origVal = newVal; + + //raise the event if it was changed + if (changed) + { + OnPropertyChanged(propertySelector); + } } - return false; } - - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 8ead6da5f8..e271774b8d 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -33,8 +33,14 @@ namespace Umbraco.Core.Models _content = getFileContent != null ? null : string.Empty; } - private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.Content); - private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.Content); + public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + } + private string _alias; private string _name; @@ -87,11 +93,7 @@ namespace Umbraco.Core.Models _alias = null; _name = null; - SetPropertyValueAndDetectChanges(o => - { - _path = SanitizePath(value); - return _path; - }, _path, PathSelector); + SetPropertyValueAndDetectChanges(SanitizePath(value), ref _path, Ps.Value.PathSelector); } } @@ -131,11 +133,9 @@ namespace Umbraco.Core.Models } set { - SetPropertyValueAndDetectChanges(o => - { - _content = value ?? string.Empty; // cannot set to null - return _content; - }, _content, ContentSelector); + SetPropertyValueAndDetectChanges( + value ?? string.Empty, // cannot set to null + ref _content, Ps.Value.ContentSelector); } } diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index 2715e5fe3a..7caefc1121 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Diagnostics; using Umbraco.Core.Persistence.Mappers; @@ -21,6 +22,7 @@ namespace Umbraco.Core.Models bool Published { get; } [Obsolete("This will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] string Language { get; set; } /// diff --git a/src/Umbraco.Core/Models/IRedirectUrl.cs b/src/Umbraco.Core/Models/IRedirectUrl.cs new file mode 100644 index 0000000000..419389f60d --- /dev/null +++ b/src/Umbraco.Core/Models/IRedirectUrl.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.Serialization; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a redirect url. + /// + public interface IRedirectUrl : IAggregateRoot, IRememberBeingDirty + { + /// + /// Gets or sets the identifier of the content item. + /// + [DataMember] + int ContentId { get; set; } + + /// + /// Gets or sets the unique key identifying the content item. + /// + [DataMember] + Guid ContentKey { get; set; } + + /// + /// Gets or sets the redirect url creation date. + /// + [DataMember] + DateTime CreateDateUtc { get; set; } + + /// + /// Gets or sets the redirect url route. + /// + /// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo. + [DataMember] + string Url { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index 0d803c26e5..50dc7d06f8 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Models.Identity Culture = Configuration.GlobalSettings.DefaultUILanguage; } - public virtual async Task GenerateUserIdentityAsync(BackOfficeUserManager manager) + public virtual async Task GenerateUserIdentityAsync(BackOfficeUserManager manager) { // NOTE the authenticationType must match the umbraco one // defined in CookieAuthenticationOptions.AuthenticationType diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index b23bbfb52a..d915bee85b 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -21,8 +21,13 @@ namespace Umbraco.Core.Models IsoCode = isoCode; } - private static readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo(x => x.IsoCode); - private static readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo(x => x.CultureName); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo(x => x.IsoCode); + public readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo(x => x.CultureName); + } /// /// Gets or sets the Iso Code for the Language @@ -31,14 +36,7 @@ namespace Umbraco.Core.Models public string IsoCode { get { return _isoCode; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _isoCode = value; - return _isoCode; - }, _isoCode, IsoCodeSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _isoCode, Ps.Value.IsoCodeSelector); } } /// @@ -48,14 +46,7 @@ namespace Umbraco.Core.Models public string CultureName { get { return _cultureName; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _cultureName = value; - return _cultureName; - }, _cultureName, CultureNameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _cultureName, Ps.Value.CultureNameSelector); } } /// diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs index a15639f886..ef5479c316 100644 --- a/src/Umbraco.Core/Models/Macro.cs +++ b/src/Umbraco.Core/Models/Macro.cs @@ -114,22 +114,27 @@ namespace Umbraco.Core.Models private List _addedProperties; private List _removedProperties; - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo UseInEditorSelector = ExpressionHelper.GetPropertyInfo(x => x.UseInEditor); - private static readonly PropertyInfo CacheDurationSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheDuration); - private static readonly PropertyInfo CacheByPageSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByPage); - private static readonly PropertyInfo CacheByMemberSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByMember); - private static readonly PropertyInfo DontRenderSelector = ExpressionHelper.GetPropertyInfo(x => x.DontRender); - private static readonly PropertyInfo ControlPathSelector = ExpressionHelper.GetPropertyInfo(x => x.ControlType); - private static readonly PropertyInfo ControlAssemblySelector = ExpressionHelper.GetPropertyInfo(x => x.ControlAssembly); - private static readonly PropertyInfo ScriptPathSelector = ExpressionHelper.GetPropertyInfo(x => x.ScriptPath); - private static readonly PropertyInfo XsltPathSelector = ExpressionHelper.GetPropertyInfo(x => x.XsltPath); - private static readonly PropertyInfo PropertiesSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo UseInEditorSelector = ExpressionHelper.GetPropertyInfo(x => x.UseInEditor); + public readonly PropertyInfo CacheDurationSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheDuration); + public readonly PropertyInfo CacheByPageSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByPage); + public readonly PropertyInfo CacheByMemberSelector = ExpressionHelper.GetPropertyInfo(x => x.CacheByMember); + public readonly PropertyInfo DontRenderSelector = ExpressionHelper.GetPropertyInfo(x => x.DontRender); + public readonly PropertyInfo ControlPathSelector = ExpressionHelper.GetPropertyInfo(x => x.ControlType); + public readonly PropertyInfo ControlAssemblySelector = ExpressionHelper.GetPropertyInfo(x => x.ControlAssembly); + public readonly PropertyInfo ScriptPathSelector = ExpressionHelper.GetPropertyInfo(x => x.ScriptPath); + public readonly PropertyInfo XsltPathSelector = ExpressionHelper.GetPropertyInfo(x => x.XsltPath); + public readonly PropertyInfo PropertiesSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); + } void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(PropertiesSelector); + OnPropertyChanged(Ps.Value.PropertiesSelector); if (e.Action == NotifyCollectionChangedAction.Add) { @@ -167,7 +172,7 @@ namespace Umbraco.Core.Models /// void PropertyDataChanged(object sender, PropertyChangedEventArgs e) { - OnPropertyChanged(PropertiesSelector); + OnPropertyChanged(Ps.Value.PropertiesSelector); } public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) @@ -204,14 +209,7 @@ namespace Umbraco.Core.Models public string Alias { get { return _alias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _alias = value.ToCleanString(CleanStringType.Alias); - return _alias; - }, _alias, AliasSelector); - } + set { SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.Alias), ref _alias, Ps.Value.AliasSelector); } } /// @@ -221,14 +219,7 @@ namespace Umbraco.Core.Models public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -238,14 +229,7 @@ namespace Umbraco.Core.Models public bool UseInEditor { get { return _useInEditor; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _useInEditor = value; - return _useInEditor; - }, _useInEditor, UseInEditorSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _useInEditor, Ps.Value.UseInEditorSelector); } } /// @@ -255,14 +239,7 @@ namespace Umbraco.Core.Models public int CacheDuration { get { return _cacheDuration; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _cacheDuration = value; - return _cacheDuration; - }, _cacheDuration, CacheDurationSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _cacheDuration, Ps.Value.CacheDurationSelector); } } /// @@ -272,14 +249,7 @@ namespace Umbraco.Core.Models public bool CacheByPage { get { return _cacheByPage; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _cacheByPage = value; - return _cacheByPage; - }, _cacheByPage, CacheByPageSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _cacheByPage, Ps.Value.CacheByPageSelector); } } /// @@ -289,14 +259,7 @@ namespace Umbraco.Core.Models public bool CacheByMember { get { return _cacheByMember; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _cacheByMember = value; - return _cacheByMember; - }, _cacheByMember, CacheByMemberSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _cacheByMember, Ps.Value.CacheByMemberSelector); } } /// @@ -306,14 +269,7 @@ namespace Umbraco.Core.Models public bool DontRender { get { return _dontRender; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _dontRender = value; - return _dontRender; - }, _dontRender, DontRenderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _dontRender, Ps.Value.DontRenderSelector); } } /// @@ -323,14 +279,7 @@ namespace Umbraco.Core.Models public string ControlType { get { return _scriptFile; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _scriptFile = value; - return _scriptFile; - }, _scriptFile, ControlPathSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _scriptFile, Ps.Value.ControlPathSelector); } } /// @@ -341,14 +290,7 @@ namespace Umbraco.Core.Models public string ControlAssembly { get { return _scriptAssembly; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _scriptAssembly = value; - return _scriptAssembly; - }, _scriptAssembly, ControlAssemblySelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _scriptAssembly, Ps.Value.ControlAssemblySelector); } } /// @@ -359,14 +301,7 @@ namespace Umbraco.Core.Models public string ScriptPath { get { return _scriptPath; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _scriptPath = value; - return _scriptPath; - }, _scriptPath, ScriptPathSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _scriptPath, Ps.Value.ScriptPathSelector); } } /// @@ -377,14 +312,7 @@ namespace Umbraco.Core.Models public string XsltPath { get { return _xslt; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _xslt = value; - return _xslt; - }, _xslt, XsltPathSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _xslt, Ps.Value.XsltPathSelector); } } /// diff --git a/src/Umbraco.Core/Models/MacroProperty.cs b/src/Umbraco.Core/Models/MacroProperty.cs index 17fef44a87..0f29c18881 100644 --- a/src/Umbraco.Core/Models/MacroProperty.cs +++ b/src/Umbraco.Core/Models/MacroProperty.cs @@ -72,11 +72,16 @@ namespace Umbraco.Core.Models private int _id; private string _editorAlias; - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); - private static readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.EditorAlias); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); + public readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.EditorAlias); + } /// /// Gets or sets the Alias of the Property @@ -85,14 +90,7 @@ namespace Umbraco.Core.Models public int Id { get { return _id; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _id = value; - return _alias; - }, _alias, IdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector); } } /// @@ -102,14 +100,7 @@ namespace Umbraco.Core.Models public string Alias { get { return _alias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _alias = value; - return _alias; - }, _alias, AliasSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } } /// @@ -119,14 +110,7 @@ namespace Umbraco.Core.Models public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -136,14 +120,7 @@ namespace Umbraco.Core.Models public int SortOrder { get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } /// @@ -159,21 +136,10 @@ namespace Umbraco.Core.Models get { return _editorAlias; } set { - SetPropertyValueAndDetectChanges(o => - { - //try to get the new mapped parameter editor - var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(value, false); - if (mapped.IsNullOrWhiteSpace() == false) - { - _editorAlias = mapped; - } - else - { - _editorAlias = value; - } - - return _editorAlias; - }, _editorAlias, PropertyTypeSelector); + //try to get the new mapped parameter editor + var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(value, false); + var newVal = mapped.IsNullOrWhiteSpace() == false ? mapped : value; + SetPropertyValueAndDetectChanges(newVal, ref _editorAlias, Ps.Value.PropertyTypeSelector); } } diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 70c21e4307..02d6b9aece 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -109,10 +109,15 @@ namespace Umbraco.Core.Models IsApproved = true; } - private static readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); - private static readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); - private static readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); - private static readonly PropertyInfo ProviderUserKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKey); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); + public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); + public readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); + public readonly PropertyInfo ProviderUserKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKey); + } /// /// Gets or sets the Username @@ -121,14 +126,7 @@ namespace Umbraco.Core.Models public string Username { get { return _username; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _username = value; - return _username; - }, _username, UsernameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _username, Ps.Value.UsernameSelector); } } /// @@ -138,14 +136,7 @@ namespace Umbraco.Core.Models public string Email { get { return _email; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _email = value; - return _email; - }, _email, EmailSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); } } /// @@ -155,14 +146,7 @@ namespace Umbraco.Core.Models public string RawPasswordValue { get { return _rawPasswordValue; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _rawPasswordValue = value; - return _rawPasswordValue; - }, _rawPasswordValue, PasswordSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); } } /// @@ -490,14 +474,7 @@ namespace Umbraco.Core.Models { return _providerUserKey; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _providerUserKey = value; - return _providerUserKey; - }, _providerUserKey, ProviderUserKeySelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _providerUserKey, Ps.Value.ProviderUserKeySelector); } } diff --git a/src/Umbraco.Core/Models/MemberGroup.cs b/src/Umbraco.Core/Models/MemberGroup.cs index ff7e05be9e..d6dc1be557 100644 --- a/src/Umbraco.Core/Models/MemberGroup.cs +++ b/src/Umbraco.Core/Models/MemberGroup.cs @@ -21,8 +21,13 @@ namespace Umbraco.Core.Models private string _name; private int _creatorId; - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + } [DataMember] public string Name @@ -30,33 +35,22 @@ namespace Umbraco.Core.Models get { return _name; } set { - SetPropertyValueAndDetectChanges(o => + if (_name != value) { - if (_name != value) - { - //if the name has changed, add the value to the additional data, - //this is required purely for event handlers to know the previous name of the group - //so we can keep the public access up to date. - AdditionalData["previousName"] = _name; - } + //if the name has changed, add the value to the additional data, + //this is required purely for event handlers to know the previous name of the group + //so we can keep the public access up to date. + AdditionalData["previousName"] = _name; + } - _name = value; - return _name; - }, _name, NameSelector); + SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } public int CreatorId { get { return _creatorId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _creatorId = value; - return _creatorId; - }, _creatorId, CreatorIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); } } public IDictionary AdditionalData { get; private set; } diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index 9000a33b54..4f79e1d231 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -30,7 +30,12 @@ namespace Umbraco.Core.Models MemberTypePropertyTypes = new Dictionary(); } - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + } /// /// The Alias of the ContentType @@ -50,13 +55,11 @@ namespace Umbraco.Core.Models // .ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase) // Need to ask Stephen - SetPropertyValueAndDetectChanges(o => - { - _alias = value == "_umbracoSystemDefaultProtectType" - ? value - : (value == null ? string.Empty : value.ToSafeAlias() ); - return _alias; - }, _alias, AliasSelector); + var newVal = value == "_umbracoSystemDefaultProtectType" + ? value + : (value == null ? string.Empty : value.ToSafeAlias()); + + SetPropertyValueAndDetectChanges(newVal, ref _alias, Ps.Value.AliasSelector); } } diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index c743893037..cadda14508 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -77,27 +77,32 @@ namespace Umbraco.Core.Models.Membership private bool _defaultToLiveEditing; - private static readonly PropertyInfo FailedPasswordAttemptsSelector = ExpressionHelper.GetPropertyInfo(x => x.FailedPasswordAttempts); - private static readonly PropertyInfo LastLockoutDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLockoutDate); - private static readonly PropertyInfo LastLoginDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLoginDate); - private static readonly PropertyInfo LastPasswordChangeDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastPasswordChangeDate); + private static readonly Lazy Ps = new Lazy(); - private static readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo(x => x.SecurityStamp); - private static readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo(x => x.SessionTimeout); - private static readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); - private static readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); - private static readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedSections); - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - - private static readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); - private static readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); - private static readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); - private static readonly PropertyInfo IsLockedOutSelector = ExpressionHelper.GetPropertyInfo(x => x.IsLockedOut); - private static readonly PropertyInfo IsApprovedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsApproved); - private static readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); + private class PropertySelectors + { + public readonly PropertyInfo FailedPasswordAttemptsSelector = ExpressionHelper.GetPropertyInfo(x => x.FailedPasswordAttempts); + public readonly PropertyInfo LastLockoutDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLockoutDate); + public readonly PropertyInfo LastLoginDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastLoginDate); + public readonly PropertyInfo LastPasswordChangeDateSelector = ExpressionHelper.GetPropertyInfo(x => x.LastPasswordChangeDate); - private static readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultToLiveEditing); - private static readonly PropertyInfo UserTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.UserType); + public readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo(x => x.SecurityStamp); + public readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo(x => x.SessionTimeout); + public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); + public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); + public readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedSections); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + + public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo(x => x.Username); + public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); + public readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo(x => x.RawPasswordValue); + public readonly PropertyInfo IsLockedOutSelector = ExpressionHelper.GetPropertyInfo(x => x.IsLockedOut); + public readonly PropertyInfo IsApprovedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsApproved); + public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); + + public readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultToLiveEditing); + public readonly PropertyInfo UserTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.UserType); + } #region Implementation of IMembershipUser @@ -113,124 +118,61 @@ namespace Umbraco.Core.Models.Membership public string Username { get { return _username; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _username = value; - return _username; - }, _username, UsernameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _username, Ps.Value.UsernameSelector); } } [DataMember] public string Email { get { return _email; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _email = value; - return _email; - }, _email, EmailSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); } } [DataMember] public string RawPasswordValue { get { return _rawPasswordValue; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _rawPasswordValue = value; - return _rawPasswordValue; - }, _rawPasswordValue, PasswordSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); } } [DataMember] public bool IsApproved { get { return _isApproved; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _isApproved = value; - return _isApproved; - }, _isApproved, IsApprovedSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _isApproved, Ps.Value.IsApprovedSelector); } } [IgnoreDataMember] public bool IsLockedOut { get { return _isLockedOut; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _isLockedOut = value; - return _isLockedOut; - }, _isLockedOut, IsLockedOutSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _isLockedOut, Ps.Value.IsLockedOutSelector); } } [IgnoreDataMember] public DateTime LastLoginDate { get { return _lastLoginDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _lastLoginDate = value; - return _lastLoginDate; - }, _lastLoginDate, LastLoginDateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _lastLoginDate, Ps.Value.LastLoginDateSelector); } } [IgnoreDataMember] public DateTime LastPasswordChangeDate { get { return _lastPasswordChangedDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _lastPasswordChangedDate = value; - return _lastPasswordChangedDate; - }, _lastPasswordChangedDate, LastPasswordChangeDateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangedDate, Ps.Value.LastPasswordChangeDateSelector); } } [IgnoreDataMember] public DateTime LastLockoutDate { get { return _lastLockoutDate; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _lastLockoutDate = value; - return _lastLockoutDate; - }, _lastLockoutDate, LastLockoutDateSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _lastLockoutDate, Ps.Value.LastLockoutDateSelector); } } [IgnoreDataMember] public int FailedPasswordAttempts { get { return _failedLoginAttempts; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _failedLoginAttempts = value; - return _failedLoginAttempts; - }, _failedLoginAttempts, FailedPasswordAttemptsSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _failedLoginAttempts, Ps.Value.FailedPasswordAttemptsSelector); } } //TODO: Figure out how to support all of this! - we cannot have NotImplementedExceptions because these get used by the IMembershipMemberService service so @@ -251,14 +193,7 @@ namespace Umbraco.Core.Models.Membership public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } public IEnumerable AllowedSections @@ -294,14 +229,7 @@ namespace Umbraco.Core.Models.Membership public string SecurityStamp { get { return _securityStamp; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _securityStamp = value; - return _securityStamp; - }, _securityStamp, SecurityStampSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _securityStamp, Ps.Value.SecurityStampSelector); } } /// @@ -330,14 +258,7 @@ namespace Umbraco.Core.Models.Membership public int SessionTimeout { get { return _sessionTimeout; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sessionTimeout = value; - return _sessionTimeout; - }, _sessionTimeout, SessionTimeoutSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sessionTimeout, Ps.Value.SessionTimeoutSelector); } } /// @@ -350,14 +271,7 @@ namespace Umbraco.Core.Models.Membership public int StartContentId { get { return _startContentId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _startContentId = value; - return _startContentId; - }, _startContentId, StartContentIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _startContentId, Ps.Value.StartContentIdSelector); } } /// @@ -370,28 +284,14 @@ namespace Umbraco.Core.Models.Membership public int StartMediaId { get { return _startMediaId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _startMediaId = value; - return _startMediaId; - }, _startMediaId, StartMediaIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _startMediaId, Ps.Value.StartMediaIdSelector); } } [DataMember] public string Language { get { return _language; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _language = value; - return _language; - }, _language, LanguageSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); } } //TODO: This should be a private set @@ -406,14 +306,7 @@ namespace Umbraco.Core.Models.Membership internal bool DefaultToLiveEditing { get { return _defaultToLiveEditing; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _defaultToLiveEditing = value; - return _defaultToLiveEditing; - }, _defaultToLiveEditing, DefaultToLiveEditingSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _defaultToLiveEditing, Ps.Value.DefaultToLiveEditingSelector); } } [IgnoreDataMember] @@ -427,11 +320,7 @@ namespace Umbraco.Core.Models.Membership throw new InvalidOperationException("Cannot assign a User Type that has not been persisted"); } - SetPropertyValueAndDetectChanges(o => - { - _userType = value; - return _userType; - }, _userType, UserTypeSelector); + SetPropertyValueAndDetectChanges(value, ref _userType, Ps.Value.UserTypeSelector); } } @@ -457,7 +346,7 @@ namespace Umbraco.Core.Models.Membership /// void SectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(AllowedSectionsSelector); + OnPropertyChanged(Ps.Value.AllowedSectionsSelector); if (e.Action == NotifyCollectionChangedAction.Add) { diff --git a/src/Umbraco.Core/Models/Membership/UserType.cs b/src/Umbraco.Core/Models/Membership/UserType.cs index 5c32e57f38..01183e9d9d 100644 --- a/src/Umbraco.Core/Models/Membership/UserType.cs +++ b/src/Umbraco.Core/Models/Membership/UserType.cs @@ -20,9 +20,14 @@ namespace Umbraco.Core.Models.Membership private string _name; private IEnumerable _permissions; - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Permissions); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Permissions); + } [DataMember] public string Alias @@ -30,11 +35,10 @@ namespace Umbraco.Core.Models.Membership get { return _alias; } set { - SetPropertyValueAndDetectChanges(o => - { - _alias = value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase); - return _alias; - }, _alias, AliasSelector); + SetPropertyValueAndDetectChanges( + value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), + ref _alias, + Ps.Value.AliasSelector); } } @@ -42,14 +46,7 @@ namespace Umbraco.Core.Models.Membership public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -64,11 +61,7 @@ namespace Umbraco.Core.Models.Membership get { return _permissions; } set { - SetPropertyValueAndDetectChanges(o => - { - _permissions = value; - return _permissions; - }, _permissions, PermissionsSelector, + SetPropertyValueAndDetectChanges(value, ref _permissions, Ps.Value.PermissionsSelector, //Custom comparer for enumerable new DelegateEqualityComparer>( (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), diff --git a/src/Umbraco.Core/Models/MigrationEntry.cs b/src/Umbraco.Core/Models/MigrationEntry.cs index e756e92629..575ee8efc3 100644 --- a/src/Umbraco.Core/Models/MigrationEntry.cs +++ b/src/Umbraco.Core/Models/MigrationEntry.cs @@ -19,35 +19,27 @@ namespace Umbraco.Core.Models _version = version; } - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.MigrationName); - private static readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.MigrationName); + public readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); + } + private string _migrationName; private SemVersion _version; public string MigrationName { get { return _migrationName; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _migrationName = value; - return _migrationName; - }, _migrationName, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _migrationName, Ps.Value.NameSelector); } } public SemVersion Version { get { return _version; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _version = value; - return _version; - }, _version, VersionSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _version, Ps.Value.VersionSelector); } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index d7c2eb92a8..dff6a712c7 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -1,11 +1,9 @@ using System; using System.Collections; -using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models { @@ -45,8 +43,42 @@ namespace Umbraco.Core.Models Value = value; } - private static readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); - private static readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); + public readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); + } + + private static readonly DelegateEqualityComparer ValueComparer = new DelegateEqualityComparer( + (o, o1) => + { + if (o == null && o1 == null) return true; + + //custom comparer for strings. + if (o is string || o1 is string) + { + //if one is null and another is empty then they are the same + if ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) + { + return true; + } + if (o == null || o1 == null) return false; + return o.Equals(o1); + } + + if (o == null || o1 == null) return false; + + //Custom comparer for enumerable if it is enumerable + var enum1 = o as IEnumerable; + var enum2 = o1 as IEnumerable; + if (enum1 != null && enum2 != null) + { + return enum1.Cast().UnsortedSequenceEqual(enum2.Cast()); + } + return o.Equals(o1); + }, o => o.GetHashCode()); /// /// Returns the instance of the tag support, by default tags are not enabled @@ -98,14 +130,14 @@ namespace Umbraco.Core.Models public Guid Version { get { return _version; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _version = value; - return _version; - }, _version, VersionSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _version, Ps.Value.VersionSelector); } + } + + private static void ThrowTypeException(object value, Type expected, string alias) + { + throw new InvalidOperationException(string.Format("Value \"{0}\" of type \"{1}\" could not be converted" + + " to type \"{2}\" which is expected by property type \"{3}\".", + value, value.GetType(), expected, alias)); } /// @@ -121,47 +153,54 @@ namespace Umbraco.Core.Models get { return _value; } set { - bool typeValidation = _propertyType.IsPropertyTypeValid(value); + var isOfExpectedType = _propertyType.IsPropertyTypeValid(value); - if (typeValidation == false) - throw new Exception( - string.Format( - "Type validation failed. The value type: '{0}' does not match the DataType in PropertyType with alias: '{1}'", - value == null ? "null" : value.GetType().Name, Alias)); - - SetPropertyValueAndDetectChanges(o => + if (isOfExpectedType == false) // isOfExpectedType is true if value is null - so if false, value is *not* null { - _value = value; - return _value; - }, _value, ValueSelector, - new DelegateEqualityComparer( - (o, o1) => + // "garbage-in", accept what we can & convert + // throw only if conversion is not possible + + var s = value.ToString(); + + switch (_propertyType.DataTypeDatabaseType) { - if (o == null && o1 == null) return true; - - //custom comparer for strings. - if (o is string || o1 is string) - { - //if one is null and another is empty then they are the same - if ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) + case DataTypeDatabaseType.Nvarchar: + case DataTypeDatabaseType.Ntext: + value = s; + break; + case DataTypeDatabaseType.Integer: + if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null + else { - return true; + var convInt = value.TryConvertTo(); + if (convInt == false) ThrowTypeException(value, typeof(int), _propertyType.Alias); + value = convInt.Result; } - if (o == null || o1 == null) return false; - return o.Equals(o1); - } - - if (o == null || o1 == null) return false; + break; + case DataTypeDatabaseType.Decimal: + if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null + else + { + var convDecimal = value.TryConvertTo(); + if (convDecimal == false) ThrowTypeException(value, typeof (decimal), _propertyType.Alias); + // need to normalize the value (change the scaling factor and remove trailing zeroes) + // because the underlying database is going to mess with the scaling factor anyways. + value = convDecimal.Result.Normalize(); + } + break; + case DataTypeDatabaseType.Date: + if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null + else + { + var convDateTime = value.TryConvertTo(); + if (convDateTime == false) ThrowTypeException(value, typeof (DateTime), _propertyType.Alias); + value = convDateTime.Result; + } + break; + } + } - //Custom comparer for enumerable if it is enumerable - var enum1 = o as IEnumerable; - var enum2 = o1 as IEnumerable; - if (enum1 != null && enum2 != null) - { - return enum1.Cast().UnsortedSequenceEqual(enum2.Cast()); - } - return o.Equals(o1); - }, o => o.GetHashCode())); + SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector, ValueComparer); } } diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index de88012c0e..68f6599e2e 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -29,12 +29,18 @@ namespace Umbraco.Core.Models PropertyTypes = propertyTypeCollection; } - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private readonly static PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyTypes); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyTypes); + } + void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(PropertyTypeCollectionSelector); + OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector); } /// @@ -44,14 +50,7 @@ namespace Umbraco.Core.Models public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -61,14 +60,7 @@ namespace Umbraco.Core.Models public int SortOrder { get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } /// diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 0649801a0c..0aebcb6544 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Text.RegularExpressions; @@ -45,9 +46,9 @@ namespace Umbraco.Core.Models public PropertyType(IDataTypeDefinition dataTypeDefinition, string propertyTypeAlias) : this(dataTypeDefinition) { - SetAlias(propertyTypeAlias); + _alias = GetAlias(propertyTypeAlias); } - + public PropertyType(string propertyEditorAlias, DataTypeDatabaseType dataTypeDatabaseType) : this(propertyEditorAlias, dataTypeDatabaseType, false) { @@ -55,7 +56,7 @@ namespace Umbraco.Core.Models public PropertyType(string propertyEditorAlias, DataTypeDatabaseType dataTypeDatabaseType, string propertyTypeAlias) : this(propertyEditorAlias, dataTypeDatabaseType, false, propertyTypeAlias) - { + { } /// @@ -83,20 +84,25 @@ namespace Umbraco.Core.Models _isExplicitDbType = isExplicitDbType; _propertyEditorAlias = propertyEditorAlias; _dataTypeDatabaseType = dataTypeDatabaseType; - SetAlias(propertyTypeAlias); + _alias = GetAlias(propertyTypeAlias); } - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo(x => x.Description); - private static readonly PropertyInfo DataTypeDefinitionIdSelector = ExpressionHelper.GetPropertyInfo(x => x.DataTypeDefinitionId); - private static readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); - private static readonly PropertyInfo DataTypeDatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DataTypeDatabaseType); - private static readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo(x => x.Mandatory); - private static readonly PropertyInfo HelpTextSelector = ExpressionHelper.GetPropertyInfo(x => x.HelpText); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private static readonly PropertyInfo ValidationRegExpSelector = ExpressionHelper.GetPropertyInfo(x => x.ValidationRegExp); - private static readonly PropertyInfo PropertyGroupIdSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyGroupId); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo(x => x.Description); + public readonly PropertyInfo DataTypeDefinitionIdSelector = ExpressionHelper.GetPropertyInfo(x => x.DataTypeDefinitionId); + public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); + public readonly PropertyInfo DataTypeDatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DataTypeDatabaseType); + public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo(x => x.Mandatory); + public readonly PropertyInfo HelpTextSelector = ExpressionHelper.GetPropertyInfo(x => x.HelpText); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo ValidationRegExpSelector = ExpressionHelper.GetPropertyInfo(x => x.ValidationRegExp); + public readonly PropertyInfo PropertyGroupIdSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyGroupId); + } /// /// Gets of Sets the Name of the PropertyType @@ -105,14 +111,7 @@ namespace Umbraco.Core.Models public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -122,14 +121,7 @@ namespace Umbraco.Core.Models public string Alias { get { return _alias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - SetAlias(value); - return _alias; - }, _alias, AliasSelector); - } + set { SetPropertyValueAndDetectChanges(GetAlias(value), ref _alias, Ps.Value.AliasSelector); } } /// @@ -139,14 +131,7 @@ namespace Umbraco.Core.Models public string Description { get { return _description; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _description = value; - return _description; - }, _description, DescriptionSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _description, Ps.Value.DescriptionSelector); } } /// @@ -157,28 +142,14 @@ namespace Umbraco.Core.Models public int DataTypeDefinitionId { get { return _dataTypeDefinitionId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _dataTypeDefinitionId = value; - return _dataTypeDefinitionId; - }, _dataTypeDefinitionId, DataTypeDefinitionIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _dataTypeDefinitionId, Ps.Value.DataTypeDefinitionIdSelector); } } [DataMember] public string PropertyEditorAlias { get { return _propertyEditorAlias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _propertyEditorAlias = value; - return _propertyEditorAlias; - }, _propertyEditorAlias, PropertyEditorAliasSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector); } } /// @@ -212,11 +183,7 @@ namespace Umbraco.Core.Models //don't allow setting this if an explicit declaration has been made in the ctor if (_isExplicitDbType) return; - SetPropertyValueAndDetectChanges(o => - { - _dataTypeDatabaseType = value; - return _dataTypeDatabaseType; - }, _dataTypeDatabaseType, DataTypeDatabaseTypeSelector); + SetPropertyValueAndDetectChanges(value, ref _dataTypeDatabaseType, Ps.Value.DataTypeDatabaseTypeSelector); } } @@ -228,14 +195,7 @@ namespace Umbraco.Core.Models internal Lazy PropertyGroupId { get { return _propertyGroupId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _propertyGroupId = value; - return _propertyGroupId; - }, _propertyGroupId, PropertyGroupIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, Ps.Value.PropertyGroupIdSelector); } } /// @@ -245,14 +205,7 @@ namespace Umbraco.Core.Models public bool Mandatory { get { return _mandatory; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _mandatory = value; - return _mandatory; - }, _mandatory, MandatorySelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector); } } /// @@ -263,14 +216,7 @@ namespace Umbraco.Core.Models public string HelpText { get { return _helpText; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _helpText = value; - return _helpText; - }, _helpText, HelpTextSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _helpText, Ps.Value.HelpTextSelector); } } /// @@ -280,14 +226,7 @@ namespace Umbraco.Core.Models public int SortOrder { get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } /// @@ -297,23 +236,16 @@ namespace Umbraco.Core.Models public string ValidationRegExp { get { return _validationRegExp; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _validationRegExp = value; - return _validationRegExp; - }, _validationRegExp, ValidationRegExpSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _validationRegExp, Ps.Value.ValidationRegExpSelector); } } - private void SetAlias(string value) + private string GetAlias(string value) { //NOTE: WE are doing this because we don't want to do a ToSafeAlias when the alias is the special case of // being prefixed with Constants.PropertyEditors.InternalGenericPropertiesPrefix // which is used internally - _alias = value.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix) + return value.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix) ? value : value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase); } @@ -366,18 +298,21 @@ namespace Umbraco.Core.Models } /// - /// Validates the Value from a Property according to its type + /// Gets a value indicating whether the value is of the expected type + /// for the property, and can be assigned to the property "as is". /// - /// - /// True if valid, otherwise false + /// The value. + /// True if the value is of the expected type for the property, + /// and can be assigned to the property "as is". Otherwise, false, to indicate + /// that some conversion is required. public bool IsPropertyTypeValid(object value) { - //Can't validate null values, so just allow it to pass the current validation + // null values are assumed to be ok if (value == null) return true; - //Check type if the type of the value match the type from the DataType/PropertyEditor - Type type = value.GetType(); + // check if the type of the value matches the type from the DataType/PropertyEditor + var valueType = value.GetType(); //TODO Add PropertyEditor Type validation when its relevant to introduce /*bool isEditorModel = value is IEditorModel; @@ -394,32 +329,29 @@ namespace Umbraco.Core.Models return argument == type; }*/ - if (PropertyEditorAlias.IsNullOrWhiteSpace() == false) + if (PropertyEditorAlias.IsNullOrWhiteSpace() == false) // fixme - always true? { - //Find DataType by Id - //IDataType dataType = DataTypesResolver.Current.GetById(DataTypeControlId); - //Check if dataType is null (meaning that the ControlId is valid) ? - //Possibly cast to BaseDataType and get the DbType from there (which might not be possible because it lives in umbraco.cms.businesslogic.datatype) ? - - //Simple validation using the DatabaseType from the DataTypeDefinition and Type of the passed in value - if (DataTypeDatabaseType == DataTypeDatabaseType.Integer && type == typeof(int)) - return true; - - if (DataTypeDatabaseType == DataTypeDatabaseType.Decimal && type == typeof(decimal)) - return true; - - if (DataTypeDatabaseType == DataTypeDatabaseType.Date && type == typeof(DateTime)) - return true; - - if (DataTypeDatabaseType == DataTypeDatabaseType.Nvarchar && type == typeof(string)) - return true; - - if (DataTypeDatabaseType == DataTypeDatabaseType.Ntext && type == typeof(string)) - return true; + // simple validation using the DatabaseType from the DataTypeDefinition + // and the Type of the passed in value + switch (DataTypeDatabaseType) + { + // fixme breaking! + case DataTypeDatabaseType.Integer: + return valueType == typeof(int); + case DataTypeDatabaseType.Decimal: + return valueType == typeof(decimal); + case DataTypeDatabaseType.Date: + return valueType == typeof(DateTime); + case DataTypeDatabaseType.Nvarchar: + return valueType == typeof(string); + case DataTypeDatabaseType.Ntext: + return valueType == typeof(string); + } } - //Fallback for simple value types when no Control Id or Database Type is set - if (type.IsPrimitive || value is string) + // fixme - never reached + makes no sense? + // fallback for simple value types when no Control Id or Database Type is set + if (valueType.IsPrimitive || value is string) return true; return false; @@ -444,15 +376,15 @@ namespace Umbraco.Core.Models var regexPattern = new Regex(ValidationRegExp); return regexPattern.IsMatch(value.ToString()); } - catch + catch { throw new Exception(string .Format("Invalid validation expression on property {0}",this.Alias)); } - + } - + //TODO: We must ensure that the property value can actually be saved based on the specified database type - + //TODO Add PropertyEditor validation when its relevant to introduce /*if (value is IEditorModel && DataTypeControlId != Guid.Empty) { @@ -470,19 +402,19 @@ namespace Umbraco.Core.Models { if (base.Equals(other)) return true; - //Check whether the PropertyType's properties are equal. + //Check whether the PropertyType's properties are equal. return Alias.InvariantEquals(other.Alias); } public override int GetHashCode() { - //Get hash code for the Name field if it is not null. + //Get hash code for the Name field if it is not null. int baseHash = base.GetHashCode(); - //Get hash code for the Alias field. + //Get hash code for the Alias field. int hashAlias = Alias.ToLowerInvariant().GetHashCode(); - //Calculate the hash code for the product. + //Calculate the hash code for the product. return baseHash ^ hashAlias; } @@ -494,7 +426,7 @@ namespace Umbraco.Core.Models //need to manually assign the Lazy value as it will not be automatically mapped if (PropertyGroupId != null) { - clone._propertyGroupId = new Lazy(() => PropertyGroupId.Value); + clone._propertyGroupId = new Lazy(() => PropertyGroupId.Value); } //this shouldn't really be needed since we're not tracking clone.ResetDirtyProperties(false); diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs index 9e12d6ab57..58e3b69394 100644 --- a/src/Umbraco.Core/Models/PublicAccessEntry.cs +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -44,7 +44,7 @@ namespace Umbraco.Core.Models void _ruleCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - OnPropertyChanged(AllowedSectionsSelector); + OnPropertyChanged(Ps.Value.AllowedSectionsSelector); //if (e.Action == NotifyCollectionChangedAction.Add) //{ @@ -68,10 +68,15 @@ namespace Umbraco.Core.Models } } - private static readonly PropertyInfo ProtectedNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ProtectedNodeId); - private static readonly PropertyInfo LoginNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.LoginNodeId); - private static readonly PropertyInfo NoAccessNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.NoAccessNodeId); - private static readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Rules); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ProtectedNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ProtectedNodeId); + public readonly PropertyInfo LoginNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.LoginNodeId); + public readonly PropertyInfo NoAccessNodeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.NoAccessNodeId); + public readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Rules); + } internal IEnumerable RemovedRules { @@ -112,42 +117,21 @@ namespace Umbraco.Core.Models public int LoginNodeId { get { return _loginNodeId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _loginNodeId = value; - return _loginNodeId; - }, _loginNodeId, LoginNodeIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _loginNodeId, Ps.Value.LoginNodeIdSelector); } } [DataMember] public int NoAccessNodeId { get { return _noAccessNodeId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _noAccessNodeId = value; - return _noAccessNodeId; - }, _noAccessNodeId, NoAccessNodeIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _noAccessNodeId, Ps.Value.NoAccessNodeIdSelector); } } [DataMember] public int ProtectedNodeId { get { return _protectedNodeId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _protectedNodeId = value; - return _protectedNodeId; - }, _protectedNodeId, ProtectedNodeIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _protectedNodeId, Ps.Value.ProtectedNodeIdSelector); } } public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) diff --git a/src/Umbraco.Core/Models/PublicAccessRule.cs b/src/Umbraco.Core/Models/PublicAccessRule.cs index c785d028d0..ed2cbdbd48 100644 --- a/src/Umbraco.Core/Models/PublicAccessRule.cs +++ b/src/Umbraco.Core/Models/PublicAccessRule.cs @@ -23,35 +23,26 @@ namespace Umbraco.Core.Models { } - private static readonly PropertyInfo RuleValueSelector = ExpressionHelper.GetPropertyInfo(x => x.RuleValue); - private static readonly PropertyInfo RuleTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.RuleType); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo RuleValueSelector = ExpressionHelper.GetPropertyInfo(x => x.RuleValue); + public readonly PropertyInfo RuleTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.RuleType); + } public Guid AccessEntryId { get; internal set; } public string RuleValue { get { return _ruleValue; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _ruleValue = value; - return _ruleValue; - }, _ruleValue, RuleValueSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _ruleValue, Ps.Value.RuleValueSelector); } } public string RuleType { get { return _ruleType; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _ruleType = value; - return _ruleType; - }, _ruleType, RuleTypeSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _ruleType, Ps.Value.RuleTypeSelector); } } diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs index 63e3104d12..c4cd28f6e0 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("contentNodeId")] [ForeignKey(typeof(NodeDto))] - [Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyData_1")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsPropertyData_1", ForColumns = "contentNodeId,versionId,propertytypeid")] public int NodeId { get; set; } [Column("versionId")] @@ -33,9 +33,23 @@ namespace Umbraco.Core.Models.Rdbms [NullSetting(NullSetting = NullSettings.Null)] public int? Integer { get; set; } + private decimal? _decimalValue; + [Column("dataDecimal")] [NullSetting(NullSetting = NullSettings.Null)] - public decimal? Decimal { get; set; } + public decimal? Decimal + { + get + { + return _decimalValue; + } + set + { + // need to normalize the value (change the scaling factor and remove trailing zeroes) + // because the underlying database probably has messed with the scaling factor. + _decimalValue = value.HasValue ? (decimal?) value.Value.Normalize() : null; + } + } [Column("dataDate")] [NullSetting(NullSetting = NullSettings.Null)] diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs index beebef9eeb..57c973fb64 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using System; +using Umbraco.Core.Persistence; namespace Umbraco.Core.Models.Rdbms { @@ -18,5 +19,8 @@ namespace Umbraco.Core.Models.Rdbms [Column("contenttypeNodeId")] public int ContentTypeNodeId { get; set; } + + [Column("PropertyGroupUniqueID")] + public Guid UniqueId { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs index 2f448860b0..d85baa2884 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs @@ -51,5 +51,8 @@ namespace Umbraco.Core.Models.Rdbms [Column("dbType")] public string DbType { get; set; } + + [Column("UniqueID")] + public Guid UniqueId { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs b/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs new file mode 100644 index 0000000000..ca65e4b9a1 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs @@ -0,0 +1,50 @@ +using System; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoRedirectUrl")] + [PrimaryKey("id", autoIncrement = false)] + [ExplicitColumns] + class RedirectUrlDto + { + public RedirectUrlDto() + { + CreateDateUtc = DateTime.UtcNow; + } + + // notes + // + // we want a unique, non-clustered index on (url ASC, contentId ASC, createDate DESC) but the + // problem is that the index key must be 900 bytes max. should we run without an index? done + // some perfs comparisons, and running with an index on a hash is only slightly slower on + // inserts, and much faster on reads, so... we have an index on a hash. + + [Column("id")] + [PrimaryKeyColumn(Name = "PK_umbracoRedirectUrl", AutoIncrement = false)] + public Guid Id { get; set; } + + [ResultColumn] + public int ContentId { get; set; } + + [Column("contentKey")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [ForeignKey(typeof(NodeDto), Column = "uniqueID")] + public Guid ContentKey { get; set; } + + [Column("createDateUtc")] + [NullSetting(NullSetting = NullSettings.NotNull)] + public DateTime CreateDateUtc { get; set; } + + [Column("url")] + [NullSetting(NullSetting = NullSettings.NotNull)] + public string Url { get; set; } + + [Column("urlHash")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRedirectUrl", ForColumns = "urlHash, contentKey, createDateUtc")] + [Length(40)] + public string UrlHash { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs b/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs index 3580fcf918..bbc63d950b 100644 --- a/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs @@ -1,11 +1,10 @@ -using Umbraco.Core.Persistence; +using System; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { - [TableName("cmsStylesheet")] - [PrimaryKey("nodeId", autoIncrement = false)] - [ExplicitColumns] + [Obsolete("This is no longer used and will be removed from Umbraco in future versions")] internal class StylesheetDto { [Column("nodeId")] diff --git a/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs b/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs index cfe3148764..bff327d286 100644 --- a/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs @@ -1,11 +1,10 @@ -using Umbraco.Core.Persistence; +using System; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { - [TableName("cmsStylesheetProperty")] - [PrimaryKey("nodeId", autoIncrement = false)] - [ExplicitColumns] + [Obsolete("This is no longer used and will be removed from Umbraco in future versions")] internal class StylesheetPropertyDto { [Column("nodeId")] diff --git a/src/Umbraco.Core/Models/RedirectUrl.cs b/src/Umbraco.Core/Models/RedirectUrl.cs new file mode 100644 index 0000000000..ff7746d0cf --- /dev/null +++ b/src/Umbraco.Core/Models/RedirectUrl.cs @@ -0,0 +1,67 @@ +using System; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models +{ + /// + /// Implements . + /// + [Serializable] + [DataContract(IsReference = true)] + public class RedirectUrl : Entity, IRedirectUrl + { + /// + /// Initializes a new instance of the class. + /// + public RedirectUrl() + { + CreateDateUtc = DateTime.UtcNow; + } + + private static readonly Lazy Ps = new Lazy(); + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo ContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentId); + public readonly PropertyInfo ContentKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ContentKey); + public readonly PropertyInfo CreateDateUtcSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDateUtc); + public readonly PropertyInfo UrlSelector = ExpressionHelper.GetPropertyInfo(x => x.Url); + } + + private int _contentId; + private Guid _contentKey; + private DateTime _createDateUtc; + private string _url; + + /// + public int ContentId + { + get { return _contentId; } + set { SetPropertyValueAndDetectChanges(value, ref _contentId, Ps.Value.ContentIdSelector); } + } + + /// + public Guid ContentKey + { + get { return _contentKey; } + set { SetPropertyValueAndDetectChanges(value, ref _contentKey, Ps.Value.ContentKeySelector); } + } + + /// + public DateTime CreateDateUtc + { + get { return _createDateUtc; } + set { SetPropertyValueAndDetectChanges(value, ref _createDateUtc, Ps.Value.CreateDateUtcSelector); } + } + + /// + public string Url + { + get { return _url; } + set { SetPropertyValueAndDetectChanges(value, ref _url, Ps.Value.UrlSelector); } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Relation.cs b/src/Umbraco.Core/Models/Relation.cs index f08e4b147e..6d383f9884 100644 --- a/src/Umbraco.Core/Models/Relation.cs +++ b/src/Umbraco.Core/Models/Relation.cs @@ -26,10 +26,15 @@ namespace Umbraco.Core.Models _relationType = relationType; } - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - private static readonly PropertyInfo ChildIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ChildId); - private static readonly PropertyInfo RelationTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.RelationType); - private static readonly PropertyInfo CommentSelector = ExpressionHelper.GetPropertyInfo(x => x.Comment); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo ChildIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ChildId); + public readonly PropertyInfo RelationTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.RelationType); + public readonly PropertyInfo CommentSelector = ExpressionHelper.GetPropertyInfo(x => x.Comment); + } /// /// Gets or sets the Parent Id of the Relation (Source) @@ -38,14 +43,7 @@ namespace Umbraco.Core.Models public int ParentId { get { return _parentId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _parentId = value; - return _parentId; - }, _parentId, ParentIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } } /// @@ -55,14 +53,7 @@ namespace Umbraco.Core.Models public int ChildId { get { return _childId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _childId = value; - return _childId; - }, _childId, ChildIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _childId, Ps.Value.ChildIdSelector); } } /// @@ -72,14 +63,7 @@ namespace Umbraco.Core.Models public IRelationType RelationType { get { return _relationType; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _relationType = value; - return _relationType; - }, _relationType, RelationTypeSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _relationType, Ps.Value.RelationTypeSelector); } } /// @@ -89,14 +73,7 @@ namespace Umbraco.Core.Models public string Comment { get { return _comment; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _comment = value; - return _comment; - }, _comment, CommentSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _comment, Ps.Value.CommentSelector); } } /// diff --git a/src/Umbraco.Core/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs index 5477628222..04ef897c1f 100644 --- a/src/Umbraco.Core/Models/RelationType.cs +++ b/src/Umbraco.Core/Models/RelationType.cs @@ -35,11 +35,16 @@ namespace Umbraco.Core.Models Name = name; } - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo IsBidirectionalSelector = ExpressionHelper.GetPropertyInfo(x => x.IsBidirectional); - private static readonly PropertyInfo ParentObjectTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentObjectType); - private static readonly PropertyInfo ChildObjectTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ChildObjectType); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo IsBidirectionalSelector = ExpressionHelper.GetPropertyInfo(x => x.IsBidirectional); + public readonly PropertyInfo ParentObjectTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentObjectType); + public readonly PropertyInfo ChildObjectTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.ChildObjectType); + } /// /// Gets or sets the Name of the RelationType @@ -48,14 +53,7 @@ namespace Umbraco.Core.Models public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// @@ -65,14 +63,7 @@ namespace Umbraco.Core.Models public string Alias { get { return _alias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _alias = value; - return _alias; - }, _alias, AliasSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } } /// @@ -82,14 +73,7 @@ namespace Umbraco.Core.Models public bool IsBidirectional { get { return _isBidrectional; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _isBidrectional = value; - return _isBidrectional; - }, _isBidrectional, IsBidirectionalSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _isBidrectional, Ps.Value.IsBidirectionalSelector); } } /// @@ -100,14 +84,7 @@ namespace Umbraco.Core.Models public Guid ParentObjectType { get { return _parentObjectType; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _parentObjectType = value; - return _parentObjectType; - }, _parentObjectType, ParentObjectTypeSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _parentObjectType, Ps.Value.ParentObjectTypeSelector); } } /// @@ -118,14 +95,7 @@ namespace Umbraco.Core.Models public Guid ChildObjectType { get { return _childObjectType; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _childObjectType = value; - return _childObjectType; - }, _childObjectType, ChildObjectTypeSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _childObjectType, Ps.Value.ChildObjectTypeSelector); } } } diff --git a/src/Umbraco.Core/Models/ServerRegistration.cs b/src/Umbraco.Core/Models/ServerRegistration.cs index cee70893d0..f68f21d759 100644 --- a/src/Umbraco.Core/Models/ServerRegistration.cs +++ b/src/Umbraco.Core/Models/ServerRegistration.cs @@ -15,10 +15,15 @@ namespace Umbraco.Core.Models private bool _isActive; private bool _isMaster; - private static readonly PropertyInfo ServerAddressSelector = ExpressionHelper.GetPropertyInfo(x => x.ServerAddress); - private static readonly PropertyInfo ServerIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.ServerIdentity); - private static readonly PropertyInfo IsActiveSelector = ExpressionHelper.GetPropertyInfo(x => x.IsActive); - private static readonly PropertyInfo IsMasterSelector = ExpressionHelper.GetPropertyInfo(x => x.IsMaster); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ServerAddressSelector = ExpressionHelper.GetPropertyInfo(x => x.ServerAddress); + public readonly PropertyInfo ServerIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.ServerIdentity); + public readonly PropertyInfo IsActiveSelector = ExpressionHelper.GetPropertyInfo(x => x.IsActive); + public readonly PropertyInfo IsMasterSelector = ExpressionHelper.GetPropertyInfo(x => x.IsMaster); + } /// /// Initialiazes a new instance of the class. @@ -69,14 +74,7 @@ namespace Umbraco.Core.Models public string ServerAddress { get { return _serverAddress; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _serverAddress = value; - return _serverAddress; - }, _serverAddress, ServerAddressSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _serverAddress, Ps.Value.ServerAddressSelector); } } /// @@ -85,14 +83,7 @@ namespace Umbraco.Core.Models public string ServerIdentity { get { return _serverIdentity; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _serverIdentity = value; - return _serverIdentity; - }, _serverIdentity, ServerIdentitySelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _serverIdentity, Ps.Value.ServerIdentitySelector); } } /// @@ -101,14 +92,7 @@ namespace Umbraco.Core.Models public bool IsActive { get { return _isActive; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _isActive = value; - return _isActive; - }, _isActive, IsActiveSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _isActive, Ps.Value.IsActiveSelector); } } /// @@ -117,14 +101,7 @@ namespace Umbraco.Core.Models public bool IsMaster { get { return _isMaster; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _isMaster = value; - return _isMaster; - }, _isMaster, IsMasterSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _isMaster, Ps.Value.IsMasterSelector); } } /// diff --git a/src/Umbraco.Core/Models/StylesheetProperty.cs b/src/Umbraco.Core/Models/StylesheetProperty.cs index fead6f033a..b0b2144044 100644 --- a/src/Umbraco.Core/Models/StylesheetProperty.cs +++ b/src/Umbraco.Core/Models/StylesheetProperty.cs @@ -25,8 +25,13 @@ namespace Umbraco.Core.Models _value = value; } - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); + } /// /// The CSS rule name that can be used by Umbraco in the back office @@ -39,14 +44,7 @@ namespace Umbraco.Core.Models public string Alias { get { return _alias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _alias = value; - return _alias; - }, _alias, AliasSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } } /// @@ -55,14 +53,7 @@ namespace Umbraco.Core.Models public string Value { get { return _value; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _value = value; - return _value; - }, _value, ValueSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); } } } diff --git a/src/Umbraco.Core/Models/Tag.cs b/src/Umbraco.Core/Models/Tag.cs index bfe4c10f42..92dfb9dee7 100644 --- a/src/Umbraco.Core/Models/Tag.cs +++ b/src/Umbraco.Core/Models/Tag.cs @@ -27,35 +27,27 @@ namespace Umbraco.Core.Models NodeCount = nodeCount; } - private static readonly PropertyInfo TextSelector = ExpressionHelper.GetPropertyInfo(x => x.Text); - private static readonly PropertyInfo GroupSelector = ExpressionHelper.GetPropertyInfo(x => x.Group); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo TextSelector = ExpressionHelper.GetPropertyInfo(x => x.Text); + public readonly PropertyInfo GroupSelector = ExpressionHelper.GetPropertyInfo(x => x.Group); + } + private string _text; private string _group; public string Text { get { return _text; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _text = value; - return _text; - }, _text, TextSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _text, Ps.Value.TextSelector); } } public string Group { get { return _group; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _group = value; - return _group; - }, _group, GroupSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _group, Ps.Value.GroupSelector); } } public int NodeCount { get; internal set; } diff --git a/src/Umbraco.Core/Models/Task.cs b/src/Umbraco.Core/Models/Task.cs index 0f58814eea..fc6350a13b 100644 --- a/src/Umbraco.Core/Models/Task.cs +++ b/src/Umbraco.Core/Models/Task.cs @@ -24,12 +24,17 @@ namespace Umbraco.Core.Models _taskType = taskType; } - private static readonly PropertyInfo ClosedSelector = ExpressionHelper.GetPropertyInfo(x => x.Closed); - private static readonly PropertyInfo TaskTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.TaskType); - private static readonly PropertyInfo EntityIdSelector = ExpressionHelper.GetPropertyInfo(x => x.EntityId); - private static readonly PropertyInfo OwnerUserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.OwnerUserId); - private static readonly PropertyInfo AssigneeUserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.AssigneeUserId); - private static readonly PropertyInfo CommentSelector = ExpressionHelper.GetPropertyInfo(x => x.Comment); + 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 @@ -38,14 +43,7 @@ namespace Umbraco.Core.Models public bool Closed { get { return _closed; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _closed = value; - return _closed; - }, _closed, ClosedSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _closed, Ps.Value.ClosedSelector); } } /// @@ -55,14 +53,7 @@ namespace Umbraco.Core.Models public TaskType TaskType { get { return _taskType; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _taskType = value; - return _taskType; - }, _taskType, TaskTypeSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _taskType, Ps.Value.TaskTypeSelector); } } /// @@ -72,14 +63,7 @@ namespace Umbraco.Core.Models public int EntityId { get { return _entityId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _entityId = value; - return _entityId; - }, _entityId, EntityIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _entityId, Ps.Value.EntityIdSelector); } } /// @@ -89,14 +73,7 @@ namespace Umbraco.Core.Models public int OwnerUserId { get { return _ownerUserId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _ownerUserId = value; - return _ownerUserId; - }, _ownerUserId, OwnerUserIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _ownerUserId, Ps.Value.OwnerUserIdSelector); } } /// @@ -106,14 +83,7 @@ namespace Umbraco.Core.Models public int AssigneeUserId { get { return _assigneeUserId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _assigneeUserId = value; - return _assigneeUserId; - }, _assigneeUserId, AssigneeUserIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _assigneeUserId, Ps.Value.AssigneeUserIdSelector); } } /// @@ -123,14 +93,7 @@ namespace Umbraco.Core.Models public string Comment { get { return _comment; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _comment = value; - return _comment; - }, _comment, CommentSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _comment, Ps.Value.CommentSelector); } } } diff --git a/src/Umbraco.Core/Models/TaskType.cs b/src/Umbraco.Core/Models/TaskType.cs index 3f753f5ad9..5a1e21e661 100644 --- a/src/Umbraco.Core/Models/TaskType.cs +++ b/src/Umbraco.Core/Models/TaskType.cs @@ -19,7 +19,12 @@ namespace Umbraco.Core.Models _alias = alias; } - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.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 @@ -28,14 +33,7 @@ namespace Umbraco.Core.Models public string Alias { get { return _alias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _alias = value; - return _alias; - }, _alias, AliasSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 6a271ad7ab..bef4384943 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -26,10 +26,15 @@ namespace Umbraco.Core.Models private string _masterTemplateAlias; private Lazy _masterTemplateId; - private static readonly PropertyInfo MasterTemplateAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.MasterTemplateAlias); - private static readonly PropertyInfo MasterTemplateIdSelector = ExpressionHelper.GetPropertyInfo>(x => x.MasterTemplateId); - private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo MasterTemplateAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.MasterTemplateAlias); + public readonly PropertyInfo MasterTemplateIdSelector = ExpressionHelper.GetPropertyInfo>(x => x.MasterTemplateId); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + } public Template(string name, string alias) : this(name, alias, (Func) null) @@ -53,57 +58,27 @@ namespace Umbraco.Core.Models public Lazy MasterTemplateId { get { return _masterTemplateId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _masterTemplateId = value; - return _masterTemplateId; - }, _masterTemplateId, MasterTemplateIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _masterTemplateId, Ps.Value.MasterTemplateIdSelector); } } public string MasterTemplateAlias { get { return _masterTemplateAlias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _masterTemplateAlias = value; - return _masterTemplateAlias; - }, _masterTemplateAlias, MasterTemplateAliasSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _masterTemplateAlias, Ps.Value.MasterTemplateAliasSelector); } } [DataMember] public new string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } [DataMember] public new string Alias { get { return _alias; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _alias = value.ToCleanString(CleanStringType.UnderscoreAlias); - return _alias; - }, _alias, AliasSelector); - - } + set { SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.UnderscoreAlias), ref _alias, Ps.Value.AliasSelector); } } /// diff --git a/src/Umbraco.Core/Models/UmbracoDomain.cs b/src/Umbraco.Core/Models/UmbracoDomain.cs index 943f96c9f9..8a3ef94d5b 100644 --- a/src/Umbraco.Core/Models/UmbracoDomain.cs +++ b/src/Umbraco.Core/Models/UmbracoDomain.cs @@ -24,51 +24,34 @@ namespace Umbraco.Core.Models private int? _languageId; private string _domainName; - private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.RootContentId); - private static readonly PropertyInfo DefaultLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.LanguageId); - private static readonly PropertyInfo DomainNameSelector = ExpressionHelper.GetPropertyInfo(x => x.DomainName); - + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.RootContentId); + public readonly PropertyInfo DefaultLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.LanguageId); + public readonly PropertyInfo DomainNameSelector = ExpressionHelper.GetPropertyInfo(x => x.DomainName); + } [DataMember] public int? LanguageId { get { return _languageId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _languageId = value; - return _languageId; - }, _languageId, DefaultLanguageSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _languageId, Ps.Value.DefaultLanguageSelector); } } [DataMember] public string DomainName { get { return _domainName; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _domainName = value; - return _domainName; - }, _domainName, DomainNameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _domainName, Ps.Value.DomainNameSelector); } } [DataMember] public int? RootContentId { get { return _contentId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _contentId = value; - return _contentId; - }, _contentId, ContentSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _contentId, Ps.Value.ContentSelector); } } public bool IsWildcard diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index 70ef056068..48752468d5 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -26,22 +26,28 @@ namespace Umbraco.Core.Models private bool _hasPendingChanges; private string _contentTypeAlias; private Guid _nodeObjectTypeId; + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + public readonly PropertyInfo HasChildrenSelector = ExpressionHelper.GetPropertyInfo(x => x.HasChildren); + public readonly PropertyInfo IsPublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsPublished); + public readonly PropertyInfo IsDraftSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDraft); + public readonly PropertyInfo HasPendingChangesSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPendingChanges); + public readonly PropertyInfo ContentTypeAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeAlias); + public readonly PropertyInfo ContentTypeIconSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeIcon); + public readonly PropertyInfo ContentTypeThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeThumbnail); + public readonly PropertyInfo NodeObjectTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.NodeObjectTypeId); + } - private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - private static readonly PropertyInfo HasChildrenSelector = ExpressionHelper.GetPropertyInfo(x => x.HasChildren); - private static readonly PropertyInfo IsPublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsPublished); - private static readonly PropertyInfo IsDraftSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDraft); - private static readonly PropertyInfo HasPendingChangesSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPendingChanges); - private static readonly PropertyInfo ContentTypeAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeAlias); - private static readonly PropertyInfo ContentTypeIconSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeIcon); - private static readonly PropertyInfo ContentTypeThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeThumbnail); - private static readonly PropertyInfo NodeObjectTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.NodeObjectTypeId); private string _contentTypeIcon; private string _contentTypeThumbnail; @@ -66,92 +72,43 @@ namespace Umbraco.Core.Models public int CreatorId { get { return _creatorId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _creatorId = value; - return _creatorId; - }, _creatorId, CreatorIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); } } public int Level { get { return _level; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _level = value; - return _level; - }, _level, LevelSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } } public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, NameSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } public int ParentId { get { return _parentId; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _parentId = value; - return _parentId; - }, _parentId, ParentIdSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } } public string Path { get { return _path; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _path = value; - return _path; - }, _path, PathSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } } public int SortOrder { get { return _sortOrder; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _sortOrder = value; - return _sortOrder; - }, _sortOrder, SortOrderSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } public bool Trashed { get { return _trashed; } - private set - { - SetPropertyValueAndDetectChanges(o => - { - _trashed = value; - return _trashed; - }, _trashed, TrashedSelector); - } + private set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); } } public IDictionary AdditionalData { get; private set; } @@ -162,12 +119,7 @@ namespace Umbraco.Core.Models get { return _hasChildren; } set { - SetPropertyValueAndDetectChanges(o => - { - _hasChildren = value; - return _hasChildren; - }, _hasChildren, HasChildrenSelector); - + SetPropertyValueAndDetectChanges(value, ref _hasChildren, Ps.Value.HasChildrenSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["HasChildren"] = value; } @@ -178,12 +130,7 @@ namespace Umbraco.Core.Models get { return _isPublished; } set { - SetPropertyValueAndDetectChanges(o => - { - _isPublished = value; - return _isPublished; - }, _isPublished, IsPublishedSelector); - + SetPropertyValueAndDetectChanges(value, ref _isPublished, Ps.Value.IsPublishedSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["IsPublished"] = value; } @@ -194,12 +141,7 @@ namespace Umbraco.Core.Models get { return _isDraft; } set { - SetPropertyValueAndDetectChanges(o => - { - _isDraft = value; - return _isDraft; - }, _isDraft, IsDraftSelector); - + SetPropertyValueAndDetectChanges(value, ref _isDraft, Ps.Value.IsDraftSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["IsDraft"] = value; } @@ -210,12 +152,7 @@ namespace Umbraco.Core.Models get { return _hasPendingChanges; } set { - SetPropertyValueAndDetectChanges(o => - { - _hasPendingChanges = value; - return _hasPendingChanges; - }, _hasPendingChanges, HasPendingChangesSelector); - + SetPropertyValueAndDetectChanges(value, ref _hasPendingChanges, Ps.Value.HasPendingChangesSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["HasPendingChanges"] = value; } @@ -226,12 +163,7 @@ namespace Umbraco.Core.Models get { return _contentTypeAlias; } set { - SetPropertyValueAndDetectChanges(o => - { - _contentTypeAlias = value; - return _contentTypeAlias; - }, _contentTypeAlias, ContentTypeAliasSelector); - + SetPropertyValueAndDetectChanges(value, ref _contentTypeAlias, Ps.Value.ContentTypeAliasSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["ContentTypeAlias"] = value; } @@ -242,12 +174,7 @@ namespace Umbraco.Core.Models get { return _contentTypeIcon; } set { - SetPropertyValueAndDetectChanges(o => - { - _contentTypeIcon = value; - return _contentTypeIcon; - }, _contentTypeIcon, ContentTypeIconSelector); - + SetPropertyValueAndDetectChanges(value, ref _contentTypeIcon, Ps.Value.ContentTypeIconSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["ContentTypeIcon"] = value; } @@ -258,12 +185,7 @@ namespace Umbraco.Core.Models get { return _contentTypeThumbnail; } set { - SetPropertyValueAndDetectChanges(o => - { - _contentTypeThumbnail = value; - return _contentTypeThumbnail; - }, _contentTypeThumbnail, ContentTypeThumbnailSelector); - + SetPropertyValueAndDetectChanges(value, ref _contentTypeThumbnail, Ps.Value.ContentTypeThumbnailSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["ContentTypeThumbnail"] = value; } @@ -274,12 +196,7 @@ namespace Umbraco.Core.Models get { return _nodeObjectTypeId; } set { - SetPropertyValueAndDetectChanges(o => - { - _nodeObjectTypeId = value; - return _nodeObjectTypeId; - }, _nodeObjectTypeId, NodeObjectTypeIdSelector); - + SetPropertyValueAndDetectChanges(value, ref _nodeObjectTypeId, Ps.Value.NodeObjectTypeIdSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data AdditionalData["NodeObjectTypeId"] = value; } diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index a8a1e80eb7..6ed424c232 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -9,15 +9,28 @@ using System.Xml; namespace Umbraco.Core { + /// + /// Provides object extension methods. + /// public static class ObjectExtensions { //private static readonly ConcurrentDictionary> ObjectFactoryCache = new ConcurrentDictionary>(); + /// + /// + /// + /// + /// + /// public static IEnumerable AsEnumerableOfOne(this T input) { return Enumerable.Repeat(input, 1); } + /// + /// + /// + /// public static void DisposeIfDisposable(this object input) { var disposable = input as IDisposable; @@ -25,7 +38,8 @@ namespace Umbraco.Core } /// - /// Provides a shortcut way of safely casting an input when you cannot guarantee the is an instance type (i.e., when the C# AS keyword is not applicable) + /// Provides a shortcut way of safely casting an input when you cannot guarantee the is + /// an instance type (i.e., when the C# AS keyword is not applicable). /// /// /// The input. @@ -63,30 +77,30 @@ namespace Umbraco.Core } /// - /// Tries to convert the input object to the output type using TypeConverters. If the destination type is a superclass of the input type, - /// if will use . + /// Tries to convert the input object to the output type using TypeConverters. If the destination + /// type is a superclass of the input type, if will use . /// /// The input. /// Type of the destination. /// public static Attempt TryConvertTo(this object input, Type destinationType) { - //if it is null and it is nullable, then return success with null - if (input == null && destinationType.IsGenericType && destinationType.GetGenericTypeDefinition() == typeof (Nullable<>)) - { - return Attempt.Succeed(null); + // if null... + if (input == null) + { + // nullable is ok + if (destinationType.IsGenericType && destinationType.GetGenericTypeDefinition() == typeof(Nullable<>)) + return Attempt.Succeed(null); + + // value type is nok, else can be null, so is ok + return Attempt.SucceedIf(destinationType.IsValueType == false, null); } - - //if its not nullable and it is a value type - if (input == null && destinationType.IsValueType) return Attempt.Fail(); - //if the type can be null, then no problem - if (input == null && destinationType.IsValueType == false) return Attempt.Succeed(null); + // easy if (destinationType == typeof(object)) return Attempt.Succeed(input); - if (input.GetType() == destinationType) return Attempt.Succeed(input); - //check for string so that overloaders of ToString() can take advantage of the conversion. + // check for string so that overloaders of ToString() can take advantage of the conversion. if (destinationType == typeof(string)) return Attempt.Succeed(input.ToString()); // if we've got a nullable of something, we try to convert directly to that thing. @@ -116,9 +130,10 @@ namespace Umbraco.Core { if (input is string) { + // try convert from string, returns an Attempt if the string could be + // processed (either succeeded or failed), else null if we need to try + // other methods var result = TryConvertToFromString(input as string, destinationType); - - // if we processed the string (succeed or fail), we're done if (result.HasValue) return result.Value; } @@ -201,90 +216,124 @@ namespace Umbraco.Core return Attempt.Fail(); } - private static Nullable> TryConvertToFromString(this string input, Type destinationType) + // returns an attempt if the string has been processed (either succeeded or failed) + // returns null if we need to try other methods + private static Attempt? TryConvertToFromString(this string input, Type destinationType) { + // easy if (destinationType == typeof(string)) return Attempt.Succeed(input); - if (string.IsNullOrEmpty(input)) + // null, empty, whitespaces + if (string.IsNullOrWhiteSpace(input)) { - if (destinationType == typeof(Boolean)) - return Attempt.Succeed(false); // special case for booleans, null/empty == false - if (destinationType == typeof(DateTime)) + if (destinationType == typeof(bool)) // null/empty = bool false + return Attempt.Succeed(false); + if (destinationType == typeof(DateTime)) // null/empty = min DateTime value return Attempt.Succeed(DateTime.MinValue); + + // cannot decide here, + // any of the types below will fail parsing and will return a failed attempt + // but anything else will not be processed and will return null + // so even though the string is null/empty we have to proceed } - // we have a non-empty string, look for type conversions in the expected order of frequency of use... + // look for type conversions in the expected order of frequency of use... if (destinationType.IsPrimitive) { - if (destinationType == typeof(Int32)) + if (destinationType == typeof(int)) // aka Int32 { - Int32 value; - return Int32.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); - } - if (destinationType == typeof(Int64)) - { - Int64 value; - return Int64.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); - } - if (destinationType == typeof(Boolean)) - { - Boolean value; - if (Boolean.TryParse(input, out value)) - return Attempt.Succeed(value); // don't declare failure so the CustomBooleanTypeConverter can try - } - else if (destinationType == typeof(Int16)) - { - Int16 value; - return Int16.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); - } - else if (destinationType == typeof(Double)) - { - Double value; - var input2 = NormalizeNumberDecimalSeparator(input); - return Double.TryParse(input2, out value) ? Attempt.Succeed(value) : Attempt.Fail(); - } - else if (destinationType == typeof(Single)) - { - Single value; + int value; + if (int.TryParse(input, out value)) return Attempt.Succeed(value); + + // because decimal 100.01m will happily convert to integer 100, it + // makes sense that string "100.01" *also* converts to integer 100. + decimal value2; var input2 = NormalizeNumberDecimalSeparator(input); - return Single.TryParse(input2, out value) ? Attempt.Succeed(value) : Attempt.Fail(); - } - else if (destinationType == typeof(Char)) + return Attempt.SucceedIf(decimal.TryParse(input2, out value2), Convert.ToInt32(value2)); + } + + if (destinationType == typeof(long)) // aka Int64 { - Char value; - return Char.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + long value; + if (long.TryParse(input, out value)) return Attempt.Succeed(value); + + // same as int + decimal value2; + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.SucceedIf(decimal.TryParse(input2, out value2), Convert.ToInt64(value2)); } - else if (destinationType == typeof(Byte)) + + // fixme - should we do the decimal trick for short, byte, unsigned? + + if (destinationType == typeof(bool)) // aka Boolean { - Byte value; - return Byte.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + bool value; + if (bool.TryParse(input, out value)) return Attempt.Succeed(value); + // don't declare failure so the CustomBooleanTypeConverter can try + return null; } - else if (destinationType == typeof(SByte)) + + if (destinationType == typeof(short)) // aka Int16 { - SByte value; - return SByte.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + short value; + return Attempt.SucceedIf(short.TryParse(input, out value), value); } - else if (destinationType == typeof(UInt32)) + + if (destinationType == typeof(double)) // aka Double { - UInt32 value; - return UInt32.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + double value; + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.SucceedIf(double.TryParse(input2, out value), value); } - else if (destinationType == typeof(UInt16)) + + if (destinationType == typeof(float)) // aka Single { - UInt16 value; - return UInt16.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + float value; + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.SucceedIf(float.TryParse(input2, out value), value); } - else if (destinationType == typeof(UInt64)) + + if (destinationType == typeof(char)) // aka Char { - UInt64 value; - return UInt64.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + char value; + return Attempt.SucceedIf(char.TryParse(input, out value), value); + } + + if (destinationType == typeof(byte)) // aka Byte + { + byte value; + return Attempt.SucceedIf(byte.TryParse(input, out value), value); + } + + if (destinationType == typeof(sbyte)) // aka SByte + { + sbyte value; + return Attempt.SucceedIf(sbyte.TryParse(input, out value), value); + } + + if (destinationType == typeof(uint)) // aka UInt32 + { + uint value; + return Attempt.SucceedIf(uint.TryParse(input, out value), value); + } + + if (destinationType == typeof(ushort)) // aka UInt16 + { + ushort value; + return Attempt.SucceedIf(ushort.TryParse(input, out value), value); + } + + if (destinationType == typeof(ulong)) // aka UInt64 + { + ulong value; + return Attempt.SucceedIf(ulong.TryParse(input, out value), value); } } else if (destinationType == typeof(Guid)) { Guid value; - return Guid.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + return Attempt.SucceedIf(Guid.TryParse(input, out value), value); } else if (destinationType == typeof(DateTime)) { @@ -307,30 +356,30 @@ namespace Umbraco.Core else if (destinationType == typeof(DateTimeOffset)) { DateTimeOffset value; - return DateTimeOffset.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + return Attempt.SucceedIf(DateTimeOffset.TryParse(input, out value), value); } else if (destinationType == typeof(TimeSpan)) { TimeSpan value; - return TimeSpan.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + return Attempt.SucceedIf(TimeSpan.TryParse(input, out value), value); } - else if (destinationType == typeof(Decimal)) + else if (destinationType == typeof(decimal)) // aka Decimal { - Decimal value; + decimal value; var input2 = NormalizeNumberDecimalSeparator(input); - return Decimal.TryParse(input2, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + return Attempt.SucceedIf(decimal.TryParse(input2, out value), value); } else if (destinationType == typeof(Version)) { Version value; - return Version.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + return Attempt.SucceedIf(Version.TryParse(input, out value), value); } // E_NOTIMPL IPAddress, BigInteger return null; // we can't decide... } - private static readonly char[] NumberDecimalSeparatorsToNormalize = new[] {'.', ','}; + private static readonly char[] NumberDecimalSeparatorsToNormalize = {'.', ','}; private static string NormalizeNumberDecimalSeparator(string s) { diff --git a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs index dc3b2f3e8b..bcc1528d3f 100644 --- a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs +++ b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs @@ -107,10 +107,10 @@ namespace Umbraco.Core.Persistence var foreignSql = _syntaxProvider.Format(tableDefinition.ForeignKeys); var indexSql = _syntaxProvider.Format(tableDefinition.Indexes); - var tableExist = _db.TableExist(tableName); + var tableExist = TableExist(tableName); if (overwrite && tableExist) { - _db.DropTable(tableName); + DropTable(tableName); tableExist = false; } diff --git a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs index 5145ec95c5..c0c7b2a777 100644 --- a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs +++ b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs @@ -42,13 +42,13 @@ namespace Umbraco.Core.Persistence { case DatabaseProviders.SqlServer: case DatabaseProviders.SqlAzure: - factory = DbProviderFactories.GetFactory("System.Data.SqlClient"); + factory = DbProviderFactories.GetFactory(Constants.DatabaseProviders.SqlServer); break; case DatabaseProviders.SqlServerCE: factory = DbProviderFactories.GetFactory("System.Data.SqlServerCe.4.0"); break; case DatabaseProviders.MySql: - factory = DbProviderFactories.GetFactory("MySql.Data.MySqlClient"); + factory = DbProviderFactories.GetFactory(Constants.DatabaseProviders.MySql); break; case DatabaseProviders.PostgreSQL: case DatabaseProviders.Oracle: diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index 72c4275541..658b8ebabe 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -102,5 +102,14 @@ namespace Umbraco.Core.Persistence } } } + + // during tests, the thread static var can leak between tests + // this method provides a way to force-reset the variable + internal void ResetForTests() + { + if (_nonHttpInstance == null) return; + _nonHttpInstance.Dispose(); + _nonHttpInstance = null; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs index d21aea33e6..5dcec8fed0 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs @@ -29,32 +29,41 @@ namespace Umbraco.Core.Persistence.Factories public IContent BuildEntity(DocumentDto dto) { - var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, _contentType) + var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, _contentType); + + try { - Id = _id, - Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId, - Name = dto.Text, - NodeName = dto.ContentVersionDto.ContentDto.NodeDto.Text, - Path = dto.ContentVersionDto.ContentDto.NodeDto.Path, - CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value, - WriterId = dto.WriterUserId, - Level = dto.ContentVersionDto.ContentDto.NodeDto.Level, - ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId, - SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder, - Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed, - Published = dto.Published, - CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - UpdateDate = dto.ContentVersionDto.VersionDate, - ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?)null, - ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?)null, - Version = dto.ContentVersionDto.VersionId, - PublishedState = dto.Published ? PublishedState.Published : PublishedState.Unpublished, - PublishedVersionGuid = dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - content.ResetDirtyProperties(false); - return content; + content.DisableChangeTracking(); + + content.Id = _id; + content.Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId; + content.Name = dto.Text; + content.NodeName = dto.ContentVersionDto.ContentDto.NodeDto.Text; + content.Path = dto.ContentVersionDto.ContentDto.NodeDto.Path; + content.CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value; + content.WriterId = dto.WriterUserId; + content.Level = dto.ContentVersionDto.ContentDto.NodeDto.Level; + content.ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId; + content.SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder; + content.Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed; + content.Published = dto.Published; + content.CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate; + content.UpdateDate = dto.ContentVersionDto.VersionDate; + content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?) null; + content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?) null; + content.Version = dto.ContentVersionDto.VersionId; + content.PublishedState = dto.Published ? PublishedState.Published : PublishedState.Unpublished; + content.PublishedVersionGuid = dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + content.ResetDirtyProperties(false); + return content; + } + finally + { + content.EnableChangeTracking(); + } } public DocumentDto BuildDto(IContent entity) diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index 54c7d8d2c9..6fa13654ad 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -21,13 +21,22 @@ namespace Umbraco.Core.Persistence.Factories public IContentType BuildContentTypeEntity(ContentTypeDto dto) { var contentType = new ContentType(dto.NodeDto.ParentId); - BuildCommonEntity(contentType, dto); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - contentType.ResetDirtyProperties(false); + try + { + contentType.DisableChangeTracking(); - return contentType; + BuildCommonEntity(contentType, dto); + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + contentType.ResetDirtyProperties(false); + return contentType; + } + finally + { + contentType.EnableChangeTracking(); + } } #endregion @@ -37,11 +46,20 @@ namespace Umbraco.Core.Persistence.Factories public IMediaType BuildMediaTypeEntity(ContentTypeDto dto) { var contentType = new MediaType(dto.NodeDto.ParentId); - BuildCommonEntity(contentType, dto); + try + { + contentType.DisableChangeTracking(); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - contentType.ResetDirtyProperties(false); + BuildCommonEntity(contentType, dto); + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + contentType.ResetDirtyProperties(false); + } + finally + { + contentType.EnableChangeTracking(); + } return contentType; } diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs index 377d70e3cb..cf487fa1a2 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs @@ -19,25 +19,35 @@ namespace Umbraco.Core.Persistence.Factories public IDataTypeDefinition BuildEntity(DataTypeDto dto) { - var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias) - { - CreateDate = dto.NodeDto.CreateDate, - DatabaseType = dto.DbType.EnumParse(true), - Id = dto.DataTypeId, - Key = dto.NodeDto.UniqueId, - Level = dto.NodeDto.Level, - UpdateDate = dto.NodeDto.CreateDate, - Name = dto.NodeDto.Text, - ParentId = dto.NodeDto.ParentId, - Path = dto.NodeDto.Path, - SortOrder = dto.NodeDto.SortOrder, - Trashed = dto.NodeDto.Trashed, - CreatorId = dto.NodeDto.UserId.Value - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - dataTypeDefinition.ResetDirtyProperties(false); - return dataTypeDefinition; + var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias); + + + try + { + dataTypeDefinition.DisableChangeTracking(); + + dataTypeDefinition.CreateDate = dto.NodeDto.CreateDate; + dataTypeDefinition.DatabaseType = dto.DbType.EnumParse(true); + dataTypeDefinition.Id = dto.DataTypeId; + dataTypeDefinition.Key = dto.NodeDto.UniqueId; + dataTypeDefinition.Level = dto.NodeDto.Level; + dataTypeDefinition.UpdateDate = dto.NodeDto.CreateDate; + dataTypeDefinition.Name = dto.NodeDto.Text; + dataTypeDefinition.ParentId = dto.NodeDto.ParentId; + dataTypeDefinition.Path = dto.NodeDto.Path; + dataTypeDefinition.SortOrder = dto.NodeDto.SortOrder; + dataTypeDefinition.Trashed = dto.NodeDto.Trashed; + dataTypeDefinition.CreatorId = dto.NodeDto.UserId.Value; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + dataTypeDefinition.ResetDirtyProperties(false); + return dataTypeDefinition; + } + finally + { + dataTypeDefinition.EnableChangeTracking(); + } } public DataTypeDto BuildDto(IDataTypeDefinition entity) diff --git a/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs b/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs index 1b9d73bdd4..4bcfea58ca 100644 --- a/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs @@ -10,15 +10,23 @@ namespace Umbraco.Core.Persistence.Factories public IDictionaryItem BuildEntity(DictionaryDto dto) { - var item = new DictionaryItem(dto.Parent, dto.Key) - { - Id = dto.PrimaryKey, - Key = dto.UniqueId - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - item.ResetDirtyProperties(false); - return item; + var item = new DictionaryItem(dto.Parent, dto.Key); + + try + { + item.DisableChangeTracking(); + + item.Id = dto.PrimaryKey; + item.Key = dto.UniqueId; + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + item.ResetDirtyProperties(false); + return item; + } + finally + { + item.EnableChangeTracking(); + } } public DictionaryDto BuildDto(IDictionaryItem entity) diff --git a/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs b/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs index 65297b9529..2a931f6069 100644 --- a/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs @@ -17,13 +17,23 @@ namespace Umbraco.Core.Persistence.Factories public IDictionaryTranslation BuildEntity(LanguageTextDto dto) { - var item = new DictionaryTranslation(dto.LanguageId, dto.Value, _uniqueId) - {Id = dto.PrimaryKey}; + var item = new DictionaryTranslation(dto.LanguageId, dto.Value, _uniqueId); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - item.ResetDirtyProperties(false); - return item; + try + { + item.DisableChangeTracking(); + + item.Id = dto.PrimaryKey; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + item.ResetDirtyProperties(false); + return item; + } + finally + { + item.EnableChangeTracking(); + } } public LanguageTextDto BuildDto(IDictionaryTranslation entity) diff --git a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs index 66b493ea4d..2ec20b08eb 100644 --- a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs @@ -12,15 +12,26 @@ namespace Umbraco.Core.Persistence.Factories public IMacro BuildEntity(MacroDto dto) { var model = new Macro(dto.Id, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.ScriptType, dto.ScriptAssembly, dto.Xslt, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.Python); - foreach (var p in dto.MacroPropertyDtos) + + + try { - model.Properties.Add(new MacroProperty(p.Id, p.Alias, p.Name, p.SortOrder, p.EditorAlias)); + model.DisableChangeTracking(); + + foreach (var p in dto.MacroPropertyDtos) + { + model.Properties.Add(new MacroProperty(p.Id, p.Alias, p.Name, p.SortOrder, p.EditorAlias)); + } + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + model.ResetDirtyProperties(false); + return model; + } + finally + { + model.EnableChangeTracking(); } - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - model.ResetDirtyProperties(false); - return model; } public MacroDto BuildDto(IMacro entity) diff --git a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs index 2d8edcac52..0fcb654cb7 100644 --- a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs @@ -29,24 +29,32 @@ namespace Umbraco.Core.Persistence.Factories public IMedia BuildEntity(ContentVersionDto dto) { - var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, _contentType) - { - Id = _id, - Key = dto.ContentDto.NodeDto.UniqueId, - Path = dto.ContentDto.NodeDto.Path, - CreatorId = dto.ContentDto.NodeDto.UserId.Value, - Level = dto.ContentDto.NodeDto.Level, - ParentId = dto.ContentDto.NodeDto.ParentId, - SortOrder = dto.ContentDto.NodeDto.SortOrder, - Trashed = dto.ContentDto.NodeDto.Trashed, - CreateDate = dto.ContentDto.NodeDto.CreateDate, - UpdateDate = dto.VersionDate, - Version = dto.VersionId - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - media.ResetDirtyProperties(false); - return media; + var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, _contentType); + + try + { + media.DisableChangeTracking(); + + media.Id = _id; + media.Key = dto.ContentDto.NodeDto.UniqueId; + media.Path = dto.ContentDto.NodeDto.Path; + media.CreatorId = dto.ContentDto.NodeDto.UserId.Value; + media.Level = dto.ContentDto.NodeDto.Level; + media.ParentId = dto.ContentDto.NodeDto.ParentId; + media.SortOrder = dto.ContentDto.NodeDto.SortOrder; + media.Trashed = dto.ContentDto.NodeDto.Trashed; + media.CreateDate = dto.ContentDto.NodeDto.CreateDate; + media.UpdateDate = dto.VersionDate; + media.Version = dto.VersionId; + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + media.ResetDirtyProperties(false); + return media; + } + finally + { + media.EnableChangeTracking(); + } } public ContentVersionDto BuildDto(IMedia entity) diff --git a/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs index a35c472f24..2901f48539 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs @@ -31,26 +31,35 @@ namespace Umbraco.Core.Persistence.Factories public IMember BuildEntity(MemberDto dto) { var member = new Member( - dto.ContentVersionDto.ContentDto.NodeDto.Text, - dto.Email,dto.LoginName,dto.Password, _contentType) + dto.ContentVersionDto.ContentDto.NodeDto.Text, + dto.Email, dto.LoginName, dto.Password, _contentType); + + try { - Id = _id, - Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId, - Path = dto.ContentVersionDto.ContentDto.NodeDto.Path, - CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value, - Level = dto.ContentVersionDto.ContentDto.NodeDto.Level, - ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId, - SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder, - Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed, - CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - UpdateDate = dto.ContentVersionDto.VersionDate, - Version = dto.ContentVersionDto.VersionId - }; - member.ProviderUserKey = member.Key; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - member.ResetDirtyProperties(false); - return member; + member.DisableChangeTracking(); + + member.Id = _id; + member.Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId; + member.Path = dto.ContentVersionDto.ContentDto.NodeDto.Path; + member.CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value; + member.Level = dto.ContentVersionDto.ContentDto.NodeDto.Level; + member.ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId; + member.SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder; + member.Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed; + member.CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate; + member.UpdateDate = dto.ContentVersionDto.VersionDate; + member.Version = dto.ContentVersionDto.VersionId; + + member.ProviderUserKey = member.Key; + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + member.ResetDirtyProperties(false); + return member; + } + finally + { + member.EnableChangeTracking(); + } } public MemberDto BuildDto(IMember entity) diff --git a/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs index 9544d170e2..a4e069e85c 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs @@ -18,18 +18,26 @@ namespace Umbraco.Core.Persistence.Factories public IMemberGroup BuildEntity(NodeDto dto) { - var template = new MemberGroup + var group = new MemberGroup(); + + try { - CreateDate = dto.CreateDate, - Id = dto.NodeId, - Key = dto.UniqueId, - Name = dto.Text - }; + group.DisableChangeTracking(); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - template.ResetDirtyProperties(false); - return template; + group.CreateDate = dto.CreateDate; + group.Id = dto.NodeId; + group.Key = dto.UniqueId; + group.Name = dto.Text; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + group.ResetDirtyProperties(false); + return group; + } + finally + { + group.EnableChangeTracking(); + } } public NodeDto BuildDto(IMemberGroup entity) diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index eebbc34eda..5e34fb8936 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -12,48 +12,56 @@ namespace Umbraco.Core.Persistence.Factories public IMemberType BuildEntity(MemberTypeReadOnlyDto dto) { var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); - - var memberType = new MemberType(dto.ParentId) - { - Alias = dto.Alias, - AllowedAsRoot = dto.AllowAtRoot, - CreateDate = dto.CreateDate, - CreatorId = dto.UserId.HasValue ? dto.UserId.Value : 0, - Description = dto.Description, - Icon = dto.Icon, - Id = dto.NodeId, - IsContainer = dto.IsContainer, - Key = dto.UniqueId.Value, - Level = dto.Level, - Name = dto.Text, - Path = dto.Path, - SortOrder = dto.SortOrder, - Thumbnail = dto.Thumbnail, - Trashed = dto.Trashed, - UpdateDate = dto.CreateDate, - AllowedContentTypes = Enumerable.Empty() - }; - var propertyTypeGroupCollection = GetPropertyTypeGroupCollection(dto, memberType, standardPropertyTypes); - memberType.PropertyGroups = propertyTypeGroupCollection; + var memberType = new MemberType(dto.ParentId); - var propertyTypes = GetPropertyTypes(dto, memberType, standardPropertyTypes); - - //By Convention we add 9 stnd PropertyTypes - This is only here to support loading of types that didn't have these conventions before. - foreach (var standardPropertyType in standardPropertyTypes) + try { - if(dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue; - - //Add the standard PropertyType to the current list - propertyTypes.Add(standardPropertyType.Value); + memberType.DisableChangeTracking(); - //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType - memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key, - new MemberTypePropertyProfileAccess(false, false)); + memberType.Alias = dto.Alias; + memberType.AllowedAsRoot = dto.AllowAtRoot; + memberType.CreateDate = dto.CreateDate; + memberType.CreatorId = dto.UserId.HasValue ? dto.UserId.Value : 0; + memberType.Description = dto.Description; + memberType.Icon = dto.Icon; + memberType.Id = dto.NodeId; + memberType.IsContainer = dto.IsContainer; + memberType.Key = dto.UniqueId.Value; + memberType.Level = dto.Level; + memberType.Name = dto.Text; + memberType.Path = dto.Path; + memberType.SortOrder = dto.SortOrder; + memberType.Thumbnail = dto.Thumbnail; + memberType.Trashed = dto.Trashed; + memberType.UpdateDate = dto.CreateDate; + memberType.AllowedContentTypes = Enumerable.Empty(); + + var propertyTypeGroupCollection = GetPropertyTypeGroupCollection(dto, memberType, standardPropertyTypes); + memberType.PropertyGroups = propertyTypeGroupCollection; + + var propertyTypes = GetPropertyTypes(dto, memberType, standardPropertyTypes); + + //By Convention we add 9 stnd PropertyTypes - This is only here to support loading of types that didn't have these conventions before. + foreach (var standardPropertyType in standardPropertyTypes) + { + if (dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue; + + //Add the standard PropertyType to the current list + propertyTypes.Add(standardPropertyType.Value); + + //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType + memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key, + new MemberTypePropertyProfileAccess(false, false)); + } + memberType.NoGroupPropertyTypes = propertyTypes; + + return memberType; + } + finally + { + memberType.EnableChangeTracking(); } - memberType.NoGroupPropertyTypes = propertyTypes; - - return memberType; } private PropertyGroupCollection GetPropertyTypeGroupCollection(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary standardProps) @@ -75,6 +83,7 @@ namespace Umbraco.Core.Persistence.Factories group.Id = groupDto.Id.Value; } + group.Key = groupDto.UniqueId; group.Name = groupDto.Text; group.SortOrder = groupDto.SortOrder; group.PropertyTypes = new PropertyTypeCollection(); @@ -114,7 +123,8 @@ namespace Umbraco.Core.Persistence.Factories ValidationRegExp = typeDto.ValidationRegExp, PropertyGroupId = new Lazy(() => tempGroupDto.Id.Value), CreateDate = memberType.CreateDate, - UpdateDate = memberType.UpdateDate + UpdateDate = memberType.UpdateDate, + Key = typeDto.UniqueId }; //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -166,7 +176,8 @@ namespace Umbraco.Core.Persistence.Factories ValidationRegExp = typeDto.ValidationRegExp, PropertyGroupId = null, CreateDate = dto.CreateDate, - UpdateDate = dto.CreateDate + UpdateDate = dto.CreateDate, + Key = typeDto.UniqueId }; propertyTypes.Add(propertyType); diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index 8d51b627ea..446bd426ad 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -42,12 +42,22 @@ namespace Umbraco.Core.Persistence.Factories : propertyType.CreatePropertyFromRawValue(propertyDataDto.GetValue, propertyDataDto.VersionId.Value, propertyDataDto.Id); - //on initial construction we don't want to have dirty properties tracked - property.CreateDate = _createDate; - property.UpdateDate = _updateDate; - // http://issues.umbraco.org/issue/U4-1946 - property.ResetDirtyProperties(false); - properties.Add(property); + try + { + //on initial construction we don't want to have dirty properties tracked + property.DisableChangeTracking(); + + property.CreateDate = _createDate; + property.UpdateDate = _updateDate; + // http://issues.umbraco.org/issue/U4-1946 + property.ResetDirtyProperties(false); + properties.Add(property); + } + finally + { + property.EnableChangeTracking(); + } + } return properties; @@ -89,7 +99,7 @@ namespace Umbraco.Core.Persistence.Factories decimal val; if (decimal.TryParse(property.Value.ToString(), out val)) { - dto.Decimal = val; + dto.Decimal = val; // property value should be normalized already } } else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Date && property.Value != null && string.IsNullOrWhiteSpace(property.Value.ToString()) == false) diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index 2dfb996bb3..e1db8dcebf 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -39,47 +39,65 @@ namespace Umbraco.Core.Persistence.Factories { var group = new PropertyGroup(); - // if the group is defined on the current content type, - // assign its identifier, else it will be zero - if (groupDto.ContentTypeNodeId == _contentTypeId) - group.Id = groupDto.Id; - - group.Name = groupDto.Text; - group.SortOrder = groupDto.SortOrder; - group.PropertyTypes = new PropertyTypeCollection(); - group.Key = groupDto.UniqueId; - - //Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded - var typeDtos = groupDto.PropertyTypeDtos.Where(x => x.Id > 0); - foreach (var typeDto in typeDtos) + try { - var tempGroupDto = groupDto; - var propertyType = _propertyTypeCtor(typeDto.DataTypeDto.PropertyEditorAlias, - typeDto.DataTypeDto.DbType.EnumParse(true), - typeDto.Alias); + group.DisableChangeTracking(); - propertyType.Alias = typeDto.Alias; - propertyType.DataTypeDefinitionId = typeDto.DataTypeId; - propertyType.Description = typeDto.Description; - propertyType.Id = typeDto.Id; - propertyType.Key = typeDto.UniqueId; - propertyType.Name = typeDto.Name; - propertyType.Mandatory = typeDto.Mandatory; - propertyType.SortOrder = typeDto.SortOrder; - propertyType.ValidationRegExp = typeDto.ValidationRegExp; - propertyType.PropertyGroupId = new Lazy(() => tempGroupDto.Id); - propertyType.CreateDate = _createDate; - propertyType.UpdateDate = _updateDate; + // if the group is defined on the current content type, + // assign its identifier, else it will be zero + if (groupDto.ContentTypeNodeId == _contentTypeId) + group.Id = groupDto.Id; + group.Name = groupDto.Text; + group.SortOrder = groupDto.SortOrder; + group.PropertyTypes = new PropertyTypeCollection(); + group.Key = groupDto.UniqueId; + + //Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded + var typeDtos = groupDto.PropertyTypeDtos.Where(x => x.Id > 0); + foreach (var typeDto in typeDtos) + { + var tempGroupDto = groupDto; + var propertyType = _propertyTypeCtor(typeDto.DataTypeDto.PropertyEditorAlias, + typeDto.DataTypeDto.DbType.EnumParse(true), + typeDto.Alias); + + try + { + propertyType.DisableChangeTracking(); + + propertyType.Alias = typeDto.Alias; + propertyType.DataTypeDefinitionId = typeDto.DataTypeId; + propertyType.Description = typeDto.Description; + propertyType.Id = typeDto.Id; + propertyType.Key = typeDto.UniqueId; + propertyType.Name = typeDto.Name; + propertyType.Mandatory = typeDto.Mandatory; + propertyType.SortOrder = typeDto.SortOrder; + propertyType.ValidationRegExp = typeDto.ValidationRegExp; + propertyType.PropertyGroupId = new Lazy(() => tempGroupDto.Id); + propertyType.CreateDate = _createDate; + propertyType.UpdateDate = _updateDate; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + propertyType.ResetDirtyProperties(false); + group.PropertyTypes.Add(propertyType); + } + finally + { + propertyType.EnableChangeTracking(); + } + } //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 - propertyType.ResetDirtyProperties(false); - group.PropertyTypes.Add(propertyType); + group.ResetDirtyProperties(false); + propertyGroups.Add(group); + } + finally + { + group.EnableChangeTracking(); } - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - group.ResetDirtyProperties(false); - propertyGroups.Add(group); } return propertyGroups; diff --git a/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs b/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs index 5d614fb6d3..47adf75d89 100644 --- a/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs @@ -16,17 +16,26 @@ namespace Umbraco.Core.Persistence.Factories public IRelation BuildEntity(RelationDto dto) { - var entity = new Relation(dto.ParentId, dto.ChildId, _relationType) + var entity = new Relation(dto.ParentId, dto.ChildId, _relationType); + + try { - Comment = dto.Comment, - CreateDate = dto.Datetime, - Id = dto.Id, - UpdateDate = dto.Datetime - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - entity.ResetDirtyProperties(false); - return entity; + entity.DisableChangeTracking(); + + entity.Comment = dto.Comment; + entity.CreateDate = dto.Datetime; + entity.Id = dto.Id; + entity.UpdateDate = dto.Datetime; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + entity.ResetDirtyProperties(false); + return entity; + } + finally + { + entity.EnableChangeTracking(); + } } public RelationDto BuildDto(IRelation entity) diff --git a/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs index 358b652d0b..98d4f30042 100644 --- a/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs @@ -9,16 +9,25 @@ namespace Umbraco.Core.Persistence.Factories public IRelationType BuildEntity(RelationTypeDto dto) { - var entity = new RelationType(dto.ChildObjectType, dto.ParentObjectType, dto.Alias) + var entity = new RelationType(dto.ChildObjectType, dto.ParentObjectType, dto.Alias); + + try { - Id = dto.Id, - IsBidirectional = dto.Dual, - Name = dto.Name - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - entity.ResetDirtyProperties(false); - return entity; + entity.DisableChangeTracking(); + + entity.Id = dto.Id; + entity.IsBidirectional = dto.Dual; + entity.Name = dto.Name; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + entity.ResetDirtyProperties(false); + return entity; + } + finally + { + entity.EnableChangeTracking(); + } } public RelationTypeDto BuildDto(IRelationType entity) diff --git a/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs b/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs index 81a6703324..d60403abea 100644 --- a/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs @@ -12,20 +12,28 @@ namespace Umbraco.Core.Persistence.Factories { public Task BuildEntity(TaskDto dto) { - var entity = new Task(new TaskType(dto.TaskTypeDto.Alias) { Id = dto.TaskTypeDto.Id }) + var entity = new Task(new TaskType(dto.TaskTypeDto.Alias) { Id = dto.TaskTypeDto.Id }); + + try { - Closed = dto.Closed, - AssigneeUserId = dto.UserId, - Comment = dto.Comment, - CreateDate = dto.DateTime, - EntityId = dto.NodeId, - Id = dto.Id, - OwnerUserId = dto.ParentUserId, - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - entity.ResetDirtyProperties(false); - return entity; + 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; + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + entity.ResetDirtyProperties(false); + return entity; + } + finally + { + entity.EnableChangeTracking(); + } } public TaskDto BuildDto(Task entity) diff --git a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs index 60cde916b6..ecff14d99c 100644 --- a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs @@ -35,23 +35,31 @@ namespace Umbraco.Core.Persistence.Factories public Template BuildEntity(TemplateDto dto, IEnumerable childDefinitions, Func getFileContent) { - var template = new Template(dto.NodeDto.Text, dto.Alias, getFileContent) - { - CreateDate = dto.NodeDto.CreateDate, - Id = dto.NodeId, - Key = dto.NodeDto.UniqueId, - Path = dto.NodeDto.Path - }; + var template = new Template(dto.NodeDto.Text, dto.Alias, getFileContent); - template.IsMasterTemplate = childDefinitions.Any(x => x.ParentId == dto.NodeId); + try + { + template.DisableChangeTracking(); - if(dto.NodeDto.ParentId > 0) - template.MasterTemplateId = new Lazy(() => dto.NodeDto.ParentId); + template.CreateDate = dto.NodeDto.CreateDate; + template.Id = dto.NodeId; + template.Key = dto.NodeDto.UniqueId; + template.Path = dto.NodeDto.Path; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - template.ResetDirtyProperties(false); - return template; + template.IsMasterTemplate = childDefinitions.Any(x => x.ParentId == dto.NodeId); + + if (dto.NodeDto.ParentId > 0) + template.MasterTemplateId = new Lazy(() => dto.NodeDto.ParentId); + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + template.ResetDirtyProperties(false); + return template; + } + finally + { + template.EnableChangeTracking(); + } } public TemplateDto BuildDto(Template entity) diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs index 660cc95029..18073c088e 100644 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs @@ -28,44 +28,52 @@ namespace Umbraco.Core.Persistence.Factories { var asDictionary = (IDictionary)d; - var entity = new UmbracoEntity(d.trashed) - { - CreateDate = d.createDate, - CreatorId = d.nodeUser, - Id = d.id, - Key = d.uniqueID, - Level = d.level, - Name = d.text, - NodeObjectTypeId = d.nodeObjectType, - ParentId = d.parentID, - Path = d.path, - SortOrder = d.sortOrder, - HasChildren = d.children > 0, - ContentTypeAlias = asDictionary.ContainsKey("alias") ? (d.alias ?? string.Empty) : string.Empty, - ContentTypeIcon = asDictionary.ContainsKey("icon") ? (d.icon ?? string.Empty) : string.Empty, - ContentTypeThumbnail = asDictionary.ContainsKey("thumbnail") ? (d.thumbnail ?? string.Empty) : string.Empty, - }; + var entity = new UmbracoEntity(d.trashed); - var publishedVersion = default(Guid); - //some content items don't have a published version - if (asDictionary.ContainsKey("publishedVersion") && asDictionary["publishedVersion"] != null) + try { - Guid.TryParse(d.publishedVersion.ToString(), out publishedVersion); - } - var newestVersion = default(Guid); - if (asDictionary.ContainsKey("newestVersion") && d.newestVersion != null) - { - Guid.TryParse(d.newestVersion.ToString(), out newestVersion); - } + entity.DisableChangeTracking(); - entity.IsPublished = publishedVersion != default(Guid) || (newestVersion != default(Guid) && publishedVersion == newestVersion); - entity.IsDraft = newestVersion != default(Guid) && (publishedVersion == default(Guid) || publishedVersion != newestVersion); - entity.HasPendingChanges = (publishedVersion != default(Guid) && newestVersion != default(Guid)) && publishedVersion != newestVersion; - - //Now we can assign the additional data! - AddAdditionalData(entity, asDictionary); - - return entity; + entity.CreateDate = d.createDate; + entity.CreatorId = d.nodeUser; + entity.Id = d.id; + entity.Key = d.uniqueID; + entity.Level = d.level; + entity.Name = d.text; + entity.NodeObjectTypeId = d.nodeObjectType; + entity.ParentId = d.parentID; + entity.Path = d.path; + entity.SortOrder = d.sortOrder; + entity.HasChildren = d.children > 0; + entity.ContentTypeAlias = asDictionary.ContainsKey("alias") ? (d.alias ?? string.Empty) : string.Empty; + entity.ContentTypeIcon = asDictionary.ContainsKey("icon") ? (d.icon ?? string.Empty) : string.Empty; + entity.ContentTypeThumbnail = asDictionary.ContainsKey("thumbnail") ? (d.thumbnail ?? string.Empty) : string.Empty; + + var publishedVersion = default(Guid); + //some content items don't have a published version + if (asDictionary.ContainsKey("publishedVersion") && asDictionary["publishedVersion"] != null) + { + Guid.TryParse(d.publishedVersion.ToString(), out publishedVersion); + } + var newestVersion = default(Guid); + if (asDictionary.ContainsKey("newestVersion") && d.newestVersion != null) + { + Guid.TryParse(d.newestVersion.ToString(), out newestVersion); + } + + entity.IsPublished = publishedVersion != default(Guid) || (newestVersion != default(Guid) && publishedVersion == newestVersion); + entity.IsDraft = newestVersion != default(Guid) && (publishedVersion == default(Guid) || publishedVersion != newestVersion); + entity.HasPendingChanges = (publishedVersion != default(Guid) && newestVersion != default(Guid)) && publishedVersion != newestVersion; + + //Now we can assign the additional data! + AddAdditionalData(entity, asDictionary); + + return entity; + } + finally + { + entity.EnableChangeTracking(); + } } public UmbracoEntity BuildEntity(EntityRepository.UmbracoEntityDto dto) diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index bf6ff7d09e..0a13c82447 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -21,36 +21,44 @@ namespace Umbraco.Core.Persistence.Factories public IUser BuildEntity(UserDto dto) { var guidId = dto.Id.ToGuid(); - var user = new User(_userType) - { - Id = dto.Id, - Key = guidId, - StartContentId = dto.ContentStartId, - StartMediaId = dto.MediaStartId.HasValue ? dto.MediaStartId.Value : -1, - RawPasswordValue = dto.Password, - Username = dto.Login, - Name = dto.UserName, - IsLockedOut = dto.NoConsole, - IsApproved = dto.Disabled == false, - Email = dto.Email, - Language = dto.UserLanguage, - SecurityStamp = dto.SecurityStampToken, - FailedPasswordAttempts = dto.FailedLoginAttempts ?? 0, - LastLockoutDate = dto.LastLockoutDate ?? DateTime.MinValue, - LastLoginDate = dto.LastLoginDate ?? DateTime.MinValue, - LastPasswordChangeDate = dto.LastPasswordChangeDate ?? DateTime.MinValue - }; + var user = new User(_userType); - foreach (var app in dto.User2AppDtos) + try { - user.AddAllowedSection(app.AppAlias); + user.DisableChangeTracking(); + + user.Id = dto.Id; + user.Key = guidId; + user.StartContentId = dto.ContentStartId; + user.StartMediaId = dto.MediaStartId.HasValue ? dto.MediaStartId.Value : -1; + user.RawPasswordValue = dto.Password; + user.Username = dto.Login; + user.Name = dto.UserName; + user.IsLockedOut = dto.NoConsole; + user.IsApproved = dto.Disabled == false; + user.Email = dto.Email; + user.Language = dto.UserLanguage; + user.SecurityStamp = dto.SecurityStampToken; + user.FailedPasswordAttempts = dto.FailedLoginAttempts ?? 0; + user.LastLockoutDate = dto.LastLockoutDate ?? DateTime.MinValue; + user.LastLoginDate = dto.LastLoginDate ?? DateTime.MinValue; + user.LastPasswordChangeDate = dto.LastPasswordChangeDate ?? DateTime.MinValue; + + foreach (var app in dto.User2AppDtos) + { + user.AddAllowedSection(app.AppAlias); + } + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + user.ResetDirtyProperties(false); + + return user; + } + finally + { + user.EnableChangeTracking(); } - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - user.ResetDirtyProperties(false); - - return user; } public UserDto BuildDto(IUser entity) diff --git a/src/Umbraco.Core/Persistence/Factories/UserTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserTypeFactory.cs index b5718b7c3b..5f9cfac994 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserTypeFactory.cs @@ -11,19 +11,27 @@ namespace Umbraco.Core.Persistence.Factories public IUserType BuildEntity(UserTypeDto dto) { - var userType = new UserType - { - Alias = dto.Alias, - Id = dto.Id, - Name = dto.Name, - Permissions = dto.DefaultPermissions.IsNullOrWhiteSpace() - ? Enumerable.Empty() - : dto.DefaultPermissions.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)) - }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - userType.ResetDirtyProperties(false); - return userType; + var userType = new UserType(); + + try + { + userType.DisableChangeTracking(); + + userType.Alias = dto.Alias; + userType.Id = dto.Id; + userType.Name = dto.Name; + userType.Permissions = dto.DefaultPermissions.IsNullOrWhiteSpace() + ? Enumerable.Empty() + : dto.DefaultPermissions.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)); + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + userType.ResetDirtyProperties(false); + return userType; + } + finally + { + userType.EnableChangeTracking(); + } } public UserTypeDto BuildDto(IUserType entity) diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index 5e07a617d4..9570024b09 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -134,13 +134,16 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"), Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = new Guid(Constants.ObjectTypes.MemberType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1045, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1045", SortOrder = 2, UniqueId = new Guid("7E3962CC-CE20-4FFC-B661-5897A894BA7E"), Text = "Multiple Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - + //TODO: We're not creating these for 7.0 //_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1039, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1039", SortOrder = 2, UniqueId = new Guid("06f349a9-c949-4b6a-8660-59c10451af42"), Text = "Ultimate Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); //_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1038, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1038", SortOrder = 2, UniqueId = new Guid("1251c96c-185c-4e9b-93f4-b48205573cbd"), Text = "Simple Editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - + //_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1042, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1042", SortOrder = 2, UniqueId = new Guid("0a452bd5-83f9-4bc3-8403-1286e13fb77e"), Text = "Macro Container", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + + // all lock objects + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.ServersLock, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1," + Constants.System.ServersLock, SortOrder = 1, UniqueId = new Guid("0AF5E610-A310-4B6F-925F-E928D5416AF7"), Text = "LOCK: Servers", NodeObjectType = Constants.ObjectTypes.LockObjectGuid, CreateDate = DateTime.Now }); } private void CreateCmsContentTypeData() @@ -237,8 +240,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 14, DataTypeId = -42, PropertyEditorAlias = Constants.PropertyEditors.DropDownListAlias, DbType = "Integer" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 15, DataTypeId = -43, PropertyEditorAlias = Constants.PropertyEditors.CheckBoxListAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 16, DataTypeId = 1034, PropertyEditorAlias = Constants.PropertyEditors.ContentPickerAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 17, DataTypeId = 1035, PropertyEditorAlias = Constants.PropertyEditors.MediaPickerAlias, DbType = "Integer" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 18, DataTypeId = 1036, PropertyEditorAlias = Constants.PropertyEditors.MemberPickerAlias, DbType = "Integer" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 17, DataTypeId = 1035, PropertyEditorAlias = Constants.PropertyEditors.MultipleMediaPickerAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 21, DataTypeId = 1040, PropertyEditorAlias = Constants.PropertyEditors.RelatedLinksAlias, DbType = "Ntext" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 22, DataTypeId = 1041, PropertyEditorAlias = Constants.PropertyEditors.TagsAlias, DbType = "Ntext" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 24, DataTypeId = 1043, PropertyEditorAlias = Constants.PropertyEditors.ImageCropperAlias, DbType = "Ntext" }); @@ -263,9 +266,9 @@ namespace Umbraco.Core.Persistence.Migrations.Initial //defaults for the member list _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -1, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "10" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -2, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "Name" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -2, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "username" }); _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -3, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "asc" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -4, Alias = "includeProperties", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "[{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -4, Alias = "includeProperties", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "[{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]" }); //layouts for the list view var cardLayout = "{\"name\": \"Grid\",\"path\": \"views/propertyeditors/listview/layouts/grid/grid.html\", \"icon\": \"icon-thumbnails-small\", \"isSystem\": 1, \"selected\": true}"; @@ -273,10 +276,10 @@ namespace Umbraco.Core.Persistence.Migrations.Initial //defaults for the media list _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -5, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "100" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -6, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "VersionDate" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -6, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "updateDate" }); _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -7, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "desc" }); _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -8, Alias = "layouts", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[" + cardLayout + "," + listLayout + "]" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -9, Alias = "includeProperties", SortOrder = 5, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[{\"alias\":\"sortOrder\",\"isSystem\":1, \"header\": \"Sort order\"},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -9, Alias = "includeProperties", SortOrder = 5, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]" }); } private void CreateUmbracoRelationTypeData() diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index 0ae044d1ee..423c847c47 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -64,8 +64,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {23, typeof (PropertyDataDto)}, {24, typeof (RelationTypeDto)}, {25, typeof (RelationDto)}, - {26, typeof (StylesheetDto)}, - {27, typeof (StylesheetPropertyDto)}, + {28, typeof (TagDto)}, {29, typeof (TagRelationshipDto)}, {31, typeof (UserTypeDto)}, @@ -80,11 +79,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {40, typeof (ServerRegistrationDto)}, {41, typeof (AccessDto)}, {42, typeof (AccessRuleDto)}, - {43, typeof(CacheInstructionDto)}, + {43, typeof (CacheInstructionDto)}, {44, typeof (ExternalLoginDto)}, {45, typeof (MigrationDto)}, {46, typeof (UmbracoDeployChecksumDto)}, - {47, typeof (UmbracoDeployDependencyDto)} + {47, typeof (UmbracoDeployDependencyDto)}, + {48, typeof (RedirectUrlDto) } }; #endregion diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs index 9af6d46fbb..47772c5c15 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs @@ -43,8 +43,13 @@ namespace Umbraco.Core.Persistence.Migrations.Initial /// public SemVersion DetermineInstalledVersionByMigrations(IMigrationEntryService migrationEntryService) { - var allMigrations = migrationEntryService.GetAll(GlobalSettings.UmbracoMigrationName); - var mostrecent = allMigrations.OrderByDescending(x => x.Version).Select(x => x.Version).FirstOrDefault(); + SemVersion mostrecent = null; + + if (ValidTables.Any(x => x.InvariantEquals("umbracoMigration"))) + { + var allMigrations = migrationEntryService.GetAll(GlobalSettings.UmbracoMigrationName); + mostrecent = allMigrations.OrderByDescending(x => x.Version).Select(x => x.Version).FirstOrDefault(); + } return mostrecent ?? new SemVersion(new Version(0, 0, 0)); } @@ -116,13 +121,19 @@ namespace Umbraco.Core.Persistence.Migrations.Initial //if the error is for umbracoAccess it must be the previous version to 7.3 since that is when it is added if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoAccess")))) { - return new Version(7, 2, 5); + return new Version(7, 2, 0); } //if the error is for umbracoDeployChecksum it must be the previous version to 7.4 since that is when it is added if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoDeployChecksum")))) { - return new Version(7, 3, 4); + return new Version(7, 3, 0); + } + + //if the error is for umbracoRedirectUrl it must be the previous version to 7.5 since that is when it is added + if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoRedirectUrl")))) + { + return new Version(7, 4, 0); } return UmbracoVersion.Current; diff --git a/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs b/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs new file mode 100644 index 0000000000..f6a603c70f --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs @@ -0,0 +1,54 @@ +using System.Linq; +using System.Text; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.Migrations.Syntax.Alter; +using Umbraco.Core.Persistence.Migrations.Syntax.Create; +using Umbraco.Core.Persistence.Migrations.Syntax.Delete; +using Umbraco.Core.Persistence.Migrations.Syntax.Execute; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations +{ + internal class LocalMigrationContext : MigrationContext + { + private readonly ISqlSyntaxProvider _sqlSyntax; + + public LocalMigrationContext(DatabaseProviders databaseProvider, Database database, ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(databaseProvider, database, logger) + { + _sqlSyntax = sqlSyntax; + } + + public IExecuteBuilder Execute + { + get { return new ExecuteBuilder(this, _sqlSyntax); } + } + + public IDeleteBuilder Delete + { + get { return new DeleteBuilder(this, _sqlSyntax); } + } + + public IAlterSyntaxBuilder Alter + { + get { return new AlterSyntaxBuilder(this, _sqlSyntax); } + } + + public ICreateBuilder Create + { + get { return new CreateBuilder(this, _sqlSyntax); } + } + + public string GetSql() + { + var sb = new StringBuilder(); + foreach (var sql in Expressions.Select(x => x.Process(Database))) + { + sb.Append(sql); + sb.AppendLine(); + sb.AppendLine("GO"); + } + return sb.ToString(); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs index 081865b9a1..51751b07ad 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs @@ -63,7 +63,7 @@ namespace Umbraco.Core.Persistence.Migrations _targetVersion = targetVersion; _productName = productName; //ensure this is null if there aren't any - _migrations = migrations.Length == 0 ? null : migrations; + _migrations = migrations == null || migrations.Length == 0 ? null : migrations; } /// diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs index 72250877db..93661c9ece 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using System; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions; namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index @@ -17,12 +18,14 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index return this; } + [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] public void OnColumn(string columnName) { var column = new IndexColumnDefinition { Name = columnName }; Expression.Index.Columns.Add(column); } + [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] public void OnColumns(params string[] columnNames) { foreach (string columnName in columnNames) diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs index f2f4280f23..fcf5038a86 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs @@ -1,8 +1,13 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index +using System; + +namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index { public interface IDeleteIndexOnColumnSyntax : IFluentSyntax { + [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] void OnColumn(string columnName); + + [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] void OnColumns(params string[] columnNames); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourOneZero/AddPreviewXmlTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourOneZero/AddPreviewXmlTable.cs new file mode 100644 index 0000000000..4e8d3165fb --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourOneZero/AddPreviewXmlTable.cs @@ -0,0 +1,32 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionFourOneZero +{ + [Migration("4.1.0", 0, GlobalSettings.UmbracoMigrationName)] + public class AddPreviewXmlTable : MigrationBase + { + public AddPreviewXmlTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + public override void Up() + { + var tableName = "cmsPreviewXml"; + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + if (tables.InvariantContains(tableName)) return; + + Create.Table(tableName) + .WithColumn("nodeId").AsInt32().NotNullable() + .WithColumn("versionId").AsGuid().NotNullable() + .WithColumn("timestamp").AsDateTime().NotNullable() + .WithColumn("xml").AsString(); + } + + public override void Down() + { } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs index 684196c2e6..749996ea36 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs @@ -32,17 +32,19 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven { if (database != null) { - var dtSql = new Sql().Select("nodeId").From().Where(dto => dto.PropertyEditorAlias == Constants.PropertyEditors.RelatedLinksAlias); + var dtSql = new Sql().Select("nodeId") + .From(SqlSyntax) + .Where(dto => dto.PropertyEditorAlias == Constants.PropertyEditors.RelatedLinksAlias); + var dataTypeIds = database.Fetch(dtSql); + if (dataTypeIds.Any() == false) return string.Empty; - if (!dataTypeIds.Any()) return string.Empty; - - // need to use dynamic, as PropertyDataDto has new properties + // need to use dynamic, as PropertyDataDto has new properties (eg decimal...) in further versions that don't exist yet var propertyData = database.Fetch("SELECT * FROM cmsPropertyData" - + " WHERE propertyTypeId in (SELECT id from cmsPropertyType where dataTypeID IN (@dataTypeIds))", new { dataTypeIds = dataTypeIds }); - if (!propertyData.Any()) return string.Empty; + + " WHERE propertyTypeId in (SELECT id from cmsPropertyType where dataTypeID IN (@dataTypeIds))", new { /*dataTypeIds =*/ dataTypeIds }); + if (propertyData.Any() == false) return string.Empty; - var nodesIdsWithProperty = propertyData.Select(x => x.NodeId).Distinct().ToArray(); + var nodesIdsWithProperty = propertyData.Select(x => (int) x.contentNodeId).Distinct().ToArray(); var cmsContentXmlEntries = new List(); //We're doing an "IN" query here but SQL server only supports 2100 query parameters so we're going to split on that @@ -54,32 +56,32 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven cmsContentXmlEntries.AddRange(database.Fetch("WHERE nodeId in (@nodeIds)", new { nodeIds = batch })); } - var propertyTypeIds = propertyData.Select(x => x.PropertyTypeId).Distinct(); + var propertyTypeIds = propertyData.Select(x => (int) x.propertytypeid).Distinct(); //NOTE: We are writing the full query because we've added a column to the PropertyTypeDto in later versions so one of the columns // won't exist yet var propertyTypes = database.Fetch("SELECT * FROM cmsPropertyType WHERE id in (@propertyTypeIds)", new { propertyTypeIds = propertyTypeIds }); - + foreach (var data in propertyData) { - if (string.IsNullOrEmpty(data.Text) == false) + if (string.IsNullOrEmpty(data.dataNtext) == false) { XmlDocument xml; //fetch the current data (that's in xml format) try { xml = new XmlDocument(); - xml.LoadXml(data.Text); + xml.LoadXml(data.dataNtext); } catch (Exception ex) { int dataId = data.id; - int dataNodeId = data.nodeId; - string dataText = data.dataNText; - Logger.Error("The data stored for property id " + dataId + " on document " + dataNodeId + + int dataNodeId = data.contentNodeId; + string dataText = data.dataNtext; + Logger.Error("The data stored for property id " + dataId + " on document " + dataNodeId + " is not valid XML, the data will be removed because it cannot be converted to the new format. The value was: " + dataText, ex); - data.dataNText = ""; + data.dataNtext = ""; database.Update("cmsPropertyData", "id", data, new[] { "dataNText" }); UpdateXmlTable(propertyTypes, data, cmsContentXmlEntries, database); @@ -113,7 +115,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven } //store the serialized data - data.dataNText = JsonConvert.SerializeObject(links); + data.dataNtext = JsonConvert.SerializeObject(links); database.Update("cmsPropertyData", "id", data, new[] { "dataNText" }); @@ -130,20 +132,20 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven throw new DataLossException("Cannot downgrade from a version 7 database to a prior version, the database schema has already been modified"); } - private static void UpdateXmlTable(List propertyTypes, PropertyDataDto data, List cmsContentXmlEntries, Database database) + private static void UpdateXmlTable(List propertyTypes, dynamic data, List cmsContentXmlEntries, Database database) { //now we need to update the cmsContentXml table - var propertyType = propertyTypes.SingleOrDefault(x => x.Id == data.PropertyTypeId); + var propertyType = propertyTypes.SingleOrDefault(x => x.Id == data.propertytypeid); if (propertyType != null) { - var xmlItem = cmsContentXmlEntries.SingleOrDefault(x => x.NodeId == data.NodeId); + var xmlItem = cmsContentXmlEntries.SingleOrDefault(x => x.NodeId == data.contentNodeId); if (xmlItem != null) { var x = XElement.Parse(xmlItem.Xml); var prop = x.Element(propertyType.Alias); if (prop != null) { - prop.ReplaceAll(new XCData(data.Text)); + prop.ReplaceAll(new XCData(data.dataNtext)); database.Update(xmlItem); } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs new file mode 100644 index 0000000000..508c0f284b --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs @@ -0,0 +1,63 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero +{ + [Migration("7.5.0", 100, GlobalSettings.UmbracoMigrationName)] + public class AddRedirectUrlTable : MigrationBase + { + public AddRedirectUrlTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + // defer, because we are making decisions based upon what's in the database + Execute.Code(MigrationCode); + } + + private string MigrationCode(Database database) + { + var umbracoRedirectUrlTableName = "umbracoRedirectUrl"; + + var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); + + var tables = SqlSyntax.GetTablesInSchema(database).ToArray(); + + if (tables.InvariantContains(umbracoRedirectUrlTableName)) + { + var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); + if (columns.Any(x => x.TableName.InvariantEquals(umbracoRedirectUrlTableName) && x.ColumnName.InvariantEquals("id") && x.DataType == "uniqueidentifier")) + return null; + localContext.Delete.Table(umbracoRedirectUrlTableName); + } + + localContext.Create.Table(umbracoRedirectUrlTableName) + .WithColumn("id").AsGuid().NotNullable().PrimaryKey("PK_" + umbracoRedirectUrlTableName) + .WithColumn("createDateUtc").AsDateTime().NotNullable() + .WithColumn("url").AsString(2048).NotNullable() + .WithColumn("contentKey").AsGuid().NotNullable() + .WithColumn("urlHash").AsString(40).NotNullable(); + + localContext.Create.Index("IX_" + umbracoRedirectUrlTableName).OnTable(umbracoRedirectUrlTableName) + .OnColumn("urlHash") + .Ascending() + .OnColumn("contentKey") + .Ascending() + .OnColumn("createDateUtc") + .Descending() + .WithOptions().NonClustered(); + + localContext.Create.ForeignKey("FK_" + umbracoRedirectUrlTableName) + .FromTable(umbracoRedirectUrlTableName).ForeignColumn("contentKey") + .ToTable("umbracoNode").PrimaryColumn("uniqueID"); + + return localContext.GetSql(); + } + + public override void Down() + { } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/EnsureServersLockObject.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/EnsureServersLockObject.cs new file mode 100644 index 0000000000..6f9d74e5db --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/EnsureServersLockObject.cs @@ -0,0 +1,57 @@ +using System; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero +{ + // This migration exists for 7.3.0 but it seems like it was not always running properly + // if you're upgrading from 7.3.0 or higher than we add this migration, if you're upgrading + // from 7.3.0 or lower then you will already get this migration in the migration to get to 7.3.0 + [Migration("7.3.0", "7.5.0", 10, GlobalSettings.UmbracoMigrationName)] + public class EnsureServersLockObject : MigrationBase + { + public EnsureServersLockObject(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + // that lock object should have been part of BaseDataCreation since 7.3.0 but + // for some reason it was not, so it was created during migrations but not during + // new installs, so for ppl that upgrade, make sure they have it + + EnsureLockObject(Constants.System.ServersLock, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers"); + } + + public override void Down() + { + // not implemented + } + + private void EnsureLockObject(int id, string uniqueId, string text) + { + var exists = Context.Database.Exists(id); + if (exists) return; + + Insert + .IntoTable("umbracoNode") + .EnableIdentityInsert() + .Row(new + { + id = id, // NodeId + trashed = false, + parentId = -1, + nodeUser = 0, + level = 1, + path = "-1," + id, + sortOrder = 0, + uniqueId = new Guid(uniqueId), + text = text, + nodeObjectType = new Guid(Constants.ObjectTypes.LockObject), + createDate = DateTime.Now + }); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs new file mode 100644 index 0000000000..96523e25e8 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero +{ + /// + /// This is here to re-remove these tables, we dropped them in 7.3 but new installs created them again so we're going to re-drop them + /// + [Migration("7.5.0", 1, GlobalSettings.UmbracoMigrationName)] + public class RemoveStylesheetDataAndTablesAgain : MigrationBase + { + public RemoveStylesheetDataAndTablesAgain(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + public override void Up() + { + // defer, because we are making decisions based upon what's in the database + Execute.Code(MigrationCode); + } + + private string MigrationCode(Database database) + { + var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); + + //Clear all stylesheet data if the tables exist + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + if (tables.InvariantContains("cmsStylesheetProperty")) + { + localContext.Delete.FromTable("cmsStylesheetProperty").AllRows(); + localContext.Delete.FromTable("umbracoNode").Row(new { nodeObjectType = new Guid(Constants.ObjectTypes.StylesheetProperty) }); + + localContext.Delete.Table("cmsStylesheetProperty"); + } + if (tables.InvariantContains("cmsStylesheet")) + { + localContext.Delete.FromTable("cmsStylesheet").AllRows(); + localContext.Delete.FromTable("umbracoNode").Row(new { nodeObjectType = new Guid(Constants.ObjectTypes.Stylesheet) }); + + localContext.Delete.Table("cmsStylesheet"); + } + + return localContext.GetSql(); + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs new file mode 100644 index 0000000000..f8e6abe42e --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero +{ + /// + /// See: http://issues.umbraco.org/issue/U4-8522 + /// + [Migration("7.5.0", 2, GlobalSettings.UmbracoMigrationName)] + public class UpdateUniqueIndexOnCmsPropertyData : MigrationBase + { + public UpdateUniqueIndexOnCmsPropertyData(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + public override void Up() + { + //Clear all stylesheet data if the tables exist + //tuple = tablename, indexname, columnname, unique + var indexes = SqlSyntax.GetDefinedIndexes(Context.Database).ToArray(); + var found = indexes.FirstOrDefault( + x => x.Item1.InvariantEquals("cmsPropertyData") + && x.Item2.InvariantEquals("IX_cmsPropertyData_1") + //we're searching for the old index which is not unique + && x.Item4 == false); + + if (found != null) + { + //Check for MySQL + if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) + { + //Use the special double nested sub query for MySQL since that is the only + //way delete sub queries works + var delPropQry = SqlSyntax.GetDeleteSubquery( + "cmsPropertyData", + "id", + new Sql("SELECT MIN(id) FROM cmsPropertyData GROUP BY contentNodeId, versionId, propertytypeid HAVING MIN(id) IS NOT NULL"), + WhereInType.NotIn); + Execute.Sql(delPropQry.SQL); + } + else + { + //NOTE: Even though the above will work for MSSQL, we are not going to execute the + // nested delete sub query logic since it will be slower and there could be a ton of property + // data here so needs to be as fast as possible. + Execute.Sql("DELETE FROM cmsPropertyData WHERE id NOT IN (SELECT MIN(id) FROM cmsPropertyData GROUP BY contentNodeId, versionId, propertytypeid HAVING MIN(id) IS NOT NULL)"); + } + + //we need to re create this index + Delete.Index("IX_cmsPropertyData_1").OnTable("cmsPropertyData"); + Create.Index("IX_cmsPropertyData_1").OnTable("cmsPropertyData") + .OnColumn("contentNodeId").Ascending() + .OnColumn("versionId").Ascending() + .OnColumn("propertytypeid").Ascending() + .WithOptions().NonClustered() + .WithOptions().Unique(); + } + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index 88f90639d1..63278889a2 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -1,10 +1,10 @@ /* PetaPoco v4.0.3 - A Tiny ORMish thing for your POCO's. * Copyright © 2011 Topten Software. All Rights Reserved. - * + * * Apache License 2.0 - http://www.toptensoftware.com/petapoco/license - * - * Special thanks to Rob Conery (@robconery) for original inspiration (ie:Massive) and for - * use of Subsonic's T4 templates, Rob Sullivan (@DataChomp) for hard core DBA advice + * + * Special thanks to Rob Conery (@robconery) for original inspiration (ie:Massive) and for + * use of Subsonic's T4 templates, Rob Sullivan (@DataChomp) for hard core DBA advice * and Adam Schroder (@schotime) for lots of suggestions, improvements and Oracle support */ @@ -88,7 +88,7 @@ namespace Umbraco.Core.Persistence } // Results from paged request - public class Page + public class Page { public long CurrentPage { get; set; } public long TotalPages { get; set; } @@ -164,7 +164,7 @@ namespace Umbraco.Core.Persistence connectionStringName = ConfigurationManager.ConnectionStrings[0].Name; // Work out connection string and provider name - var providerName = "System.Data.SqlClient"; + var providerName = Constants.DatabaseProviders.SqlServer; if (ConfigurationManager.ConnectionStrings[connectionStringName] != null) { if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName)) @@ -181,7 +181,7 @@ namespace Umbraco.Core.Persistence CommonConstruct(); } - enum DBType + internal enum DBType { SqlServer, SqlServerCE, @@ -202,7 +202,7 @@ namespace Umbraco.Core.Persistence if (_providerName != null) _factory = DbProviderFactories.GetFactory(_providerName); - + string dbtype = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name; if (dbtype.StartsWith("MySql")) _dbType = DBType.MySql; @@ -385,7 +385,7 @@ namespace Umbraco.Core.Persistence return "SET TRANSACTION ISOLATION LEVEL READ COMMITTED"; } } - + // Helper to handle named parameters from object properties static Regex rxParams = new Regex(@"(? args_dest) @@ -425,8 +425,8 @@ namespace Umbraco.Core.Persistence } // Expand collections to parameter lists - if ((arg_val as System.Collections.IEnumerable) != null && - (arg_val as string) == null && + if ((arg_val as System.Collections.IEnumerable) != null && + (arg_val as string) == null && (arg_val as byte[]) == null) { var sb = new StringBuilder(); @@ -487,10 +487,10 @@ namespace Umbraco.Core.Persistence } else if (t == typeof(string)) { - // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. + // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. //Set before attempting to set Size, or Size will always max out at 4000 if ((item as string).Length + 1 > 4000 && p.GetType().Name == "SqlCeParameter") - p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); + p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); p.Size = (item as string).Length + 1; if(p.Size < 4000) @@ -676,12 +676,12 @@ namespace Umbraco.Core.Persistence public bool ForceDateTimesToUtc { get; set; } // Return a typed list of pocos - public List Fetch(string sql, params object[] args) + public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } - public List Fetch(Sql sql) + public List Fetch(Sql sql) { return Fetch(sql.SQL, sql.Arguments); } @@ -726,7 +726,43 @@ namespace Umbraco.Core.Persistence return true; } - public void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) + /// + /// NOTE: This is a custom mod of PetaPoco!! This builds the paging sql for different db providers + /// + /// + /// + /// + /// + /// + /// + /// + /// + internal virtual void BuildSqlDbSpecificPagingQuery(DBType databaseType, long skip, long take, string sql, string sqlSelectRemoved, string sqlOrderBy, ref object[] args, out string sqlPage) + { + if (databaseType == DBType.SqlServer || databaseType == DBType.Oracle) + { + sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, ""); + if (rxDistinct.IsMatch(sqlSelectRemoved)) + { + sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner"; + } + sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}", + sqlOrderBy == null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1); + args = args.Concat(new object[] { skip, skip + take }).ToArray(); + } + else if (databaseType == DBType.SqlServerCE) + { + sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1); + args = args.Concat(new object[] { skip, take }).ToArray(); + } + else + { + sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1); + args = args.Concat(new object[] { take, skip }).ToArray(); + } + } + + public void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) { // Add auto select clause if (EnableAutoSelect) @@ -734,38 +770,16 @@ namespace Umbraco.Core.Persistence // Split the SQL into the bits we need string sqlSelectRemoved, sqlOrderBy; - if (!SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy)) + if (SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy) == false) throw new Exception("Unable to parse SQL statement for paged query"); if (_dbType == DBType.Oracle && sqlSelectRemoved.StartsWith("*")) throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id"); - - // Build the SQL for the actual final result - if (_dbType == DBType.SqlServer || _dbType == DBType.Oracle) - { - sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, ""); - if (rxDistinct.IsMatch(sqlSelectRemoved)) - { - sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner"; - } - sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}", - sqlOrderBy==null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1); - args = args.Concat(new object[] { skip, skip+take }).ToArray(); - } - else if (_dbType == DBType.SqlServerCE) - { - sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1); - args = args.Concat(new object[] { skip, take }).ToArray(); - } - else - { - sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1); - args = args.Concat(new object[] { take, skip }).ToArray(); - } - + + BuildSqlDbSpecificPagingQuery(_dbType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage); } - // Fetch a page - public Page Page(long page, long itemsPerPage, string sql, params object[] args) + // Fetch a page + public Page Page(long page, long itemsPerPage, string sql, params object[] args) { string sqlCount, sqlPage; BuildPageQueries((page-1)*itemsPerPage, itemsPerPage, sql, ref args, out sqlCount, out sqlPage); @@ -791,7 +805,7 @@ namespace Umbraco.Core.Persistence return result; } - public Page Page(long page, long itemsPerPage, Sql sql) + public Page Page(long page, long itemsPerPage, Sql sql) { return Page(page, itemsPerPage, sql.SQL, sql.Arguments); } @@ -820,7 +834,7 @@ namespace Umbraco.Core.Persistence } // Return an enumerable collection of pocos - public IEnumerable Query(string sql, params object[] args) + public IEnumerable Query(string sql, params object[] args) { if (EnableAutoSelect) sql = AddSelectClause(sql); @@ -1033,7 +1047,7 @@ namespace Umbraco.Core.Persistence } private List Delegates { get; set; } private Delegate GetItem(int index) { return Delegates[index]; } - + /// /// Calls the delegate at the specified index and returns its values /// @@ -1068,7 +1082,7 @@ namespace Umbraco.Core.Persistence // Create a multi-poco factory Func CreateMultiPocoFactory(Type[] types, string sql, IDataReader r) - { + { // Call each delegate var dels = new List(); int pos = 0; @@ -1088,7 +1102,7 @@ namespace Umbraco.Core.Persistence static Dictionary AutoMappers = new Dictionary(); static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim(); - // Get (or create) the multi-poco factory for a query + // Get (or create) the multi-poco factory for a query Func GetMultiPocoFactory(Type[] types, string sql, IDataReader r) { // Build a key string (this is crap, should address this at some point) @@ -1113,8 +1127,8 @@ namespace Umbraco.Core.Persistence if (MultiPocoFactories.TryGetValue(key, out oFactory)) { //mpFactory = oFactory; - return (Func)oFactory; - } + return (Func)oFactory; + } } finally { @@ -1130,9 +1144,9 @@ namespace Umbraco.Core.Persistence if (MultiPocoFactories.TryGetValue(key, out oFactory)) { return (Func)oFactory; - } - - // Create the factory + } + + // Create the factory var factory = CreateMultiPocoFactory(types, sql, r); MultiPocoFactories.Add(key, factory); @@ -1207,54 +1221,54 @@ namespace Umbraco.Core.Persistence } } - - public IEnumerable Query(Sql sql) + + public IEnumerable Query(Sql sql) { return Query(sql.SQL, sql.Arguments); } - public bool Exists(object primaryKey) + public bool Exists(object primaryKey) { return FirstOrDefault(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey) != null; } - public T Single(object primaryKey) + public T Single(object primaryKey) { return Single(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); } - public T SingleOrDefault(object primaryKey) + public T SingleOrDefault(object primaryKey) { return SingleOrDefault(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); } - public T Single(string sql, params object[] args) + public T Single(string sql, params object[] args) { return Query(sql, args).Single(); } - public T SingleOrDefault(string sql, params object[] args) + public T SingleOrDefault(string sql, params object[] args) { return Query(sql, args).SingleOrDefault(); } - public T First(string sql, params object[] args) + public T First(string sql, params object[] args) { return Query(sql, args).First(); } - public T FirstOrDefault(string sql, params object[] args) + public T FirstOrDefault(string sql, params object[] args) { return Query(sql, args).FirstOrDefault(); } - public T Single(Sql sql) + public T Single(Sql sql) { return Query(sql).Single(); } - public T SingleOrDefault(Sql sql) + public T SingleOrDefault(Sql sql) { return Query(sql).SingleOrDefault(); } - public T First(Sql sql) + public T First(Sql sql) { return Query(sql).First(); } - public T FirstOrDefault(Sql sql) + public T FirstOrDefault(Sql sql) { return Query(sql).FirstOrDefault(); } @@ -1285,7 +1299,7 @@ namespace Umbraco.Core.Persistence return Insert(tableName, primaryKeyName, true, poco); } - // Insert a poco into a table. If the poco has a property with the same name + // Insert a poco into a table. If the poco has a property with the same name // as the primary key the id of the new record is assigned to it. Either way, // the new id is returned. public object Insert(string tableName, string primaryKeyName, bool autoIncrement, object poco) @@ -1721,7 +1735,7 @@ namespace Umbraco.Core.Persistence { cmd.CommandTimeout = CommandTimeout; } - + // Call hook OnExecutingCommand(cmd); @@ -1779,8 +1793,8 @@ namespace Umbraco.Core.Persistence public class ExpandoColumn : PocoColumn { public override void SetValue(object target, object val) { (target as IDictionary)[ColumnName]=val; } - public override object GetValue(object target) - { + public override object GetValue(object target) + { object val=null; (target as IDictionary).TryGetValue(ColumnName, out val); return val; @@ -1803,7 +1817,7 @@ namespace Umbraco.Core.Persistence } static readonly ObjectCache ObjectCache = new MemoryCache("NPoco"); - + } public class PocoData @@ -1812,7 +1826,7 @@ namespace Umbraco.Core.Persistence internal static bool UseLongKeys = false; //USE ONLY FOR TESTING - default is one hr internal static int SlidingExpirationSeconds = 3600; - + public static PocoData ForObject(object o, string primaryKeyName) { var t = o.GetType(); @@ -1836,7 +1850,7 @@ namespace Umbraco.Core.Persistence #endif return ForType(t); } - + public static PocoData ForType(Type t) { #if !PETAPOCO_NO_DYNAMIC @@ -1856,7 +1870,7 @@ namespace Umbraco.Core.Persistence InnerLock.ExitReadLock(); } - + // Cache it InnerLock.EnterWriteLock(); try @@ -1959,7 +1973,7 @@ namespace Umbraco.Core.Persistence public Delegate GetFactory(string sql, string connString, bool ForceDateTimesToUtc, int firstColumn, int countColumns, IDataReader r) { - //TODO: It would be nice to remove the irrelevant SQL parts - for a mapping operation anything after the SELECT clause isn't required. + //TODO: It would be nice to remove the irrelevant SQL parts - for a mapping operation anything after the SELECT clause isn't required. // This would ensure less duplicate entries that get cached, currently both of these queries would be cached even though they are // returning the same structured data: // SELECT * FROM MyTable ORDER BY MyColumn @@ -1981,7 +1995,7 @@ namespace Umbraco.Core.Persistence combiner.AddInt(countColumns); key = combiner.GetCombinedHashCode(); } - + var objectCache = _managedCache.GetCache(); @@ -2029,7 +2043,7 @@ namespace Umbraco.Core.Persistence il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter? if (converter != null) - il.Emit(OpCodes.Pop); // obj, obj, fieldname, + il.Emit(OpCodes.Pop); // obj, obj, fieldname, il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null if (converter != null) { @@ -2169,7 +2183,7 @@ namespace Umbraco.Core.Persistence // return it var del = m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type)); - + return del; }; @@ -2178,7 +2192,7 @@ namespace Umbraco.Core.Persistence // the line belows returns existing item or adds the new value if it doesn't exist var value = (Lazy)objectCache.AddOrGetExisting(key, newValue, new CacheItemPolicy { - //sliding expiration of 1 hr, if the same key isn't used in this + //sliding expiration of 1 hr, if the same key isn't used in this // timeframe it will be removed from the cache SlidingExpiration = new TimeSpan(0, 0, SlidingExpirationSeconds) }); @@ -2272,7 +2286,7 @@ namespace Umbraco.Core.Persistence public TableInfo TableInfo { get; private set; } public Dictionary Columns { get; private set; } static System.Threading.ReaderWriterLockSlim InnerLock = new System.Threading.ReaderWriterLockSlim(); - + /// /// Returns a report of the current cache being utilized by PetaPoco /// @@ -2286,7 +2300,7 @@ namespace Umbraco.Core.Persistence foreach (var pocoData in m_PocoDatas) { sb.AppendFormat("\t{0}\n", pocoData.Key); - sb.AppendFormat("\t\tTable:{0} - Col count:{1}\n", pocoData.Value.TableInfo.TableName, pocoData.Value.QueryColumns.Length); + sb.AppendFormat("\t\tTable:{0} - Col count:{1}\n", pocoData.Value.TableInfo.TableName, pocoData.Value.QueryColumns.Length); } var cache = managedCache.GetCache(); @@ -2299,7 +2313,7 @@ namespace Umbraco.Core.Persistence totalBytes = Encoding.Unicode.GetByteCount(keys); sb.AppendFormat("\tTotal byte for keys:{0}\n", totalBytes); - + sb.AppendLine("\tAll Poco cache items:"); foreach (var item in cache) @@ -2315,8 +2329,8 @@ namespace Umbraco.Core.Persistence // Member variables - string _connectionString; - string _providerName; + readonly string _connectionString; + readonly string _providerName; DbProviderFactory _factory; IDbConnection _sharedConnection; IDbTransaction _transaction; @@ -2417,6 +2431,7 @@ namespace Umbraco.Core.Persistence else _rhs = sql; + _sqlFinal = null; return this; } diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs index 6b8e7b46ff..a72621d1a5 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs @@ -203,7 +203,8 @@ namespace Umbraco.Core.Persistence { //if it is sql ce or it is a sql server version less than 2008, we need to do individual inserts. var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider; - if ((sqlServerSyntax != null && (int)sqlServerSyntax.VersionName.Value < (int)SqlServerVersionName.V2008) + + if ((sqlServerSyntax != null && (int)sqlServerSyntax.GetVersionName(db) < (int)SqlServerVersionName.V2008) || SqlSyntaxContext.SqlSyntaxProvider is SqlCeSyntaxProvider) { //SqlCe doesn't support bulk insert statements! diff --git a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs index 3cbd70803d..04f4147155 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs @@ -22,8 +22,7 @@ namespace Umbraco.Core.Persistence public static Sql From(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.From(sqlSyntax.GetQuotedTableName(tableName)); } @@ -51,11 +50,10 @@ namespace Umbraco.Core.Persistence public static Sql OrderBy(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlSyntax) { var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnName = column.FirstAttribute().Name; + var columnName = column.GetColumnName(); var type = typeof(TColumn); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); //need to ensure the order by is in brackets, see: https://github.com/toptensoftware/PetaPoco/issues/177 var syntax = string.Format("({0}.{1})", @@ -74,13 +72,13 @@ namespace Umbraco.Core.Persistence public static Sql OrderByDescending(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlSyntax) { var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnName = column.FirstAttribute().Name; + var columnName = column.GetColumnName(); var type = typeof(TColumn); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); - var syntax = string.Format("{0}.{1} DESC", + //need to ensure the order by is in brackets, see: https://github.com/toptensoftware/PetaPoco/issues/177 + var syntax = string.Format("({0}.{1}) DESC", sqlSyntax.GetQuotedTableName(tableName), sqlSyntax.GetQuotedColumnName(columnName)); @@ -96,7 +94,7 @@ namespace Umbraco.Core.Persistence public static Sql GroupBy(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlProvider) { var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnName = column.FirstAttribute().Name; + var columnName = column.GetColumnName(); return sql.GroupBy(sqlProvider.GetQuotedColumnName(columnName)); } @@ -110,8 +108,7 @@ namespace Umbraco.Core.Persistence public static Sql.SqlJoinClause InnerJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.InnerJoin(sqlSyntax.GetQuotedTableName(tableName)); } @@ -125,8 +122,7 @@ namespace Umbraco.Core.Persistence public static Sql.SqlJoinClause LeftJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.LeftJoin(sqlSyntax.GetQuotedTableName(tableName)); } @@ -140,8 +136,7 @@ namespace Umbraco.Core.Persistence public static Sql.SqlJoinClause LeftOuterJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.LeftOuterJoin(sqlSyntax.GetQuotedTableName(tableName)); } @@ -155,8 +150,7 @@ namespace Umbraco.Core.Persistence public static Sql.SqlJoinClause RightJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) { var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + var tableName = type.GetTableName(); return sql.RightJoin(sqlSyntax.GetQuotedTableName(tableName)); } @@ -173,13 +167,14 @@ namespace Umbraco.Core.Persistence { var leftType = typeof(TLeft); var rightType = typeof(TRight); - var leftTableName = leftType.FirstAttribute().Value; - var rightTableName = rightType.FirstAttribute().Value; + var leftTableName = leftType.GetTableName(); + var rightTableName = rightType.GetTableName(); - var left = ExpressionHelper.FindProperty(leftMember) as PropertyInfo; - var right = ExpressionHelper.FindProperty(rightMember) as PropertyInfo; - var leftColumnName = left.FirstAttribute().Name; - var rightColumnName = right.FirstAttribute().Name; + var leftColumn = ExpressionHelper.FindProperty(leftMember) as PropertyInfo; + var rightColumn = ExpressionHelper.FindProperty(rightMember) as PropertyInfo; + + var leftColumnName = leftColumn.GetColumnName(); + var rightColumnName = rightColumn.GetColumnName(); string onClause = string.Format("{0}.{1} = {2}.{3}", sqlSyntax.GetQuotedTableName(leftTableName), @@ -193,5 +188,20 @@ namespace Umbraco.Core.Persistence { return sql.Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x + " DESC").ToArray()))); } + + 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 attr == null || string.IsNullOrWhiteSpace(attr.Value) ? string.Empty : attr.Value; + } + + private static string GetColumnName(this PropertyInfo column) + { + var attr = column.FirstAttribute(); + return attr == null || string.IsNullOrWhiteSpace(attr.Name) ? column.Name : attr.Name; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index 4679b9b2ad..0960acc9e5 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -440,6 +440,55 @@ namespace Umbraco.Core.Persistence.Querying } return HandleStringComparison(visitedObjectForMethod, compareValue, m.Method.Name, colType); + + case "Replace": + string searchValue; + + if (methodArgs[0].NodeType != ExpressionType.Constant) + { + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[0], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + searchValue = getter().ToString(); + } + else + { + searchValue = methodArgs[0].ToString(); + } + + if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[0].Type)) + { + throw new NotSupportedException("An array Contains method is not supported"); + } + + string replaceValue; + + if (methodArgs[1].NodeType != ExpressionType.Constant) + { + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[1], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + replaceValue = getter().ToString(); + } + else + { + replaceValue = methodArgs[1].ToString(); + } + + if (methodArgs[1].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[1].Type)) + { + throw new NotSupportedException("An array Contains method is not supported"); + } + + SqlParameters.Add(RemoveQuote(searchValue)); + + SqlParameters.Add(RemoveQuote(replaceValue)); + + return string.Format("replace({0}, @{1}, @{2})", visitedObjectForMethod, SqlParameters.Count - 2, SqlParameters.Count - 1); //case "Substring": // var startIndex = Int32.Parse(args[0].ToString()) + 1; // if (args.Count == 2) @@ -501,7 +550,7 @@ namespace Umbraco.Core.Persistence.Querying //case "As": // return string.Format("{0} As {1}", r, // GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString())))); - + default: throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name); diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 5f96cad114..9222769247 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Linq.Expressions; using System.Net.Http.Headers; using System.Text; +using System.Xml; using System.Xml.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Dynamics; @@ -63,9 +64,9 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new { Id = id }) .Where(x => x.Newest) - .OrderByDescending(x => x.VersionDate); + .OrderByDescending(x => x.VersionDate, SqlSyntax); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; @@ -143,6 +144,7 @@ namespace Umbraco.Core.Persistence.Repositories { var list = new List { + "DELETE FROM umbracoRedirectUrl WHERE contentKey IN (SELECT uniqueID FROM umbracoNode WHERE id = @Id)", "DELETE FROM cmsTask WHERE nodeId = @Id", "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", @@ -687,6 +689,77 @@ namespace Umbraco.Core.Persistence.Repositories } } + + /// + /// This builds the Xml document used for the XML cache + /// + /// + public XmlDocument BuildXmlCache() + { + //TODO: This is what we should do , but converting to use XDocument would be breaking unless we convert + // to XmlDocument at the end of this, but again, this would be bad for memory... though still not nearly as + // bad as what is happening before! + // We'll keep using XmlDocument for now though, but XDocument xml generation is much faster: + // https://blogs.msdn.microsoft.com/codejunkie/2008/10/08/xmldocument-vs-xelement-performance/ + // I think we already have code in here to convert XDocument to XmlDocument but in case we don't here + // it is: https://blogs.msdn.microsoft.com/marcelolr/2009/03/13/fast-way-to-convert-xmldocument-into-xdocument/ + + //// Prepare an XmlDocument with an appropriate inline DTD to match + //// the expected content + //var parent = new XElement("root", new XAttribute("id", "-1")); + //var xmlDoc = new XDocument( + // new XDocumentType("root", null, null, DocumentType.GenerateDtd()), + // parent); + + var xmlDoc = new XmlDocument(); + var doctype = xmlDoc.CreateDocumentType("root", null, null, + ApplicationContext.Current.Services.ContentTypeService.GetContentTypesDtd()); + xmlDoc.AppendChild(doctype); + var parent = xmlDoc.CreateElement("root"); + var pIdAtt = xmlDoc.CreateAttribute("id"); + pIdAtt.Value = "-1"; + parent.Attributes.Append(pIdAtt); + xmlDoc.AppendChild(parent); + + const string sql = @"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.xml, umbracoNode.level from umbracoNode +inner join cmsContentXml on cmsContentXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type +where umbracoNode.id in (select cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1) +order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; + + XmlElement last = null; + + //NOTE: Query creates a reader - does not load all into memory + foreach (var row in Database.Query(sql, new { type = new Guid(Constants.ObjectTypes.Document) })) + { + string parentId = ((int)row.parentID).ToInvariantString(); + string xml = row.xml; + int sortOrder = row.sortOrder; + + //if the parentid is changing + if (last != null && last.GetAttribute("parentID") != parentId) + { + parent = xmlDoc.GetElementById(parentId); + if (parent == null) + { + //Need to short circuit here, if the parent is not there it means that the parent is unpublished + // and therefore the child is not published either so cannot be included in the xml cache + continue; + } + } + + var xmlDocFragment = xmlDoc.CreateDocumentFragment(); + xmlDocFragment.InnerXml = xml; + + last = (XmlElement)parent.AppendChild(xmlDocFragment); + + // fix sortOrder - see notes in UpdateSortOrder + last.Attributes["sortOrder"].Value = sortOrder.ToInvariantString(); + } + + return xmlDoc; + + } + public int CountPublished() { var sql = GetBaseQuery(true).Where(x => x.Trashed == false) @@ -771,22 +844,22 @@ namespace Umbraco.Core.Persistence.Repositories /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null) { //NOTE: This uses the GetBaseQuery method but that does not take into account the required 'newest' field which is // what we always require for a paged result, so we'll ensure it's included in the filter - - var args = new List(); - var sbWhere = new StringBuilder("AND (cmsDocument.newest = 1)"); - - if (filter.IsNullOrWhiteSpace() == false) + + var filterSql = new Sql().Append("AND (cmsDocument.newest = 1)"); + if (filter != null) { - sbWhere.Append(" AND (cmsDocument." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ")"); - args.Add("%" + filter + "%"); + foreach (var filterClaus in filter.GetWhereClauses()) + { + filterSql.Append(string.Format("AND ({0})", filterClaus.Item1), filterClaus.Item2); + } } - - Func> filterCallback = () => new Tuple(sbWhere.ToString(), args.ToArray()); + + Func> filterCallback = () => new Tuple(filterSql.SQL, filterSql.Arguments); return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsDocument", "nodeId"), diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index dc85ad3a33..d86f77168e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -251,8 +251,8 @@ AND umbracoNode.id <> @id", var nodeDto = dto.NodeDto; Database.Update(nodeDto); + // look up ContentType entry to get PrimaryKey for updating the DTO // fixme - why? we are UPDATING so we should ALREADY have a PK! - //Look up ContentType entry to get PrimaryKey for updating the DTO var dtoPk = Database.First("WHERE nodeId = @Id", new { Id = entity.Id }); dto.PrimaryKey = dtoPk.PrimaryKey; Database.Update(dto); @@ -262,31 +262,30 @@ AND umbracoNode.id <> @id", foreach (var composition in entity.ContentTypeComposition) Database.Insert(new ContentType2ContentTypeDto { ParentId = composition.Id, ChildId = entity.Id }); - //Removing a ContentType from a composition (U4-1690) - //1. Find content based on the current ContentType: entity.Id - //2. Find all PropertyTypes on the ContentType that was removed - tracked id (key) - //3. Remove properties based on property types from the removed content type where the content ids correspond to those found in step one + // removing a ContentType from a composition (U4-1690) + // 1. Find content based on the current ContentType: entity.Id + // 2. Find all PropertyTypes on the ContentType that was removed - tracked id (key) + // 3. Remove properties based on property types from the removed content type where the content ids correspond to those found in step one var compositionBase = entity as ContentTypeCompositionBase; if (compositionBase != null && compositionBase.RemovedContentTypeKeyTracker != null && compositionBase.RemovedContentTypeKeyTracker.Any()) { - //Find Content based on the current ContentType + // find Content based on the current ContentType var sql = new Sql(); sql.Select("*") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) + .From(SqlSyntax) + .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) .Where(x => x.NodeObjectType == new Guid(Constants.ObjectTypes.Document)) .Where(x => x.ContentTypeId == entity.Id); - var contentDtos = Database.Fetch(sql); - //Loop through all tracked keys, which corresponds to the ContentTypes that has been removed from the composition + + // loop through all tracked keys, which corresponds to the ContentTypes that has been removed from the composition foreach (var key in compositionBase.RemovedContentTypeKeyTracker) { - //Find PropertyTypes for the removed ContentType + // find PropertyTypes for the removed ContentType var propertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = key }); - //Loop through the Content that is based on the current ContentType in order to remove the Properties that are - //based on the PropertyTypes that belong to the removed ContentType. + // loop through the Content that is based on the current ContentType in order to remove the Properties that are + // based on the PropertyTypes that belong to the removed ContentType. foreach (var contentDto in contentDtos) { foreach (var propertyType in propertyTypes) @@ -294,51 +293,47 @@ AND umbracoNode.id <> @id", var nodeId = contentDto.NodeId; var propertyTypeId = propertyType.Id; var propertySql = new Sql().Select("cmsPropertyData.id") - .From() - .InnerJoin() - .On( - left => left.PropertyTypeId, right => right.Id) - .Where(x => x.NodeId == nodeId) - .Where(x => x.Id == propertyTypeId); + .From(SqlSyntax) + .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) + .Where(x => x.NodeId == nodeId) + .Where(x => x.Id == propertyTypeId); - //Finally delete the properties that match our criteria for removing a ContentType from the composition + // finally delete the properties that match our criteria for removing a ContentType from the composition Database.Delete(new Sql("WHERE id IN (" + propertySql.SQL + ")", propertySql.Arguments)); } } } } - //Delete the allowed content type entries before adding the updated collection - Database.Delete("WHERE Id = @Id", new { Id = entity.Id }); - //Insert collection of allowed content types + // delete the allowed content type entries before re-inserting the collectino of allowed content types + Database.Delete("WHERE Id = @Id", new { entity.Id }); foreach (var allowedContentType in entity.AllowedContentTypes) { Database.Insert(new ContentTypeAllowedContentTypeDto - { - Id = entity.Id, - AllowedId = allowedContentType.Id.Value, - SortOrder = allowedContentType.SortOrder - }); + { + Id = entity.Id, + AllowedId = allowedContentType.Id.Value, + SortOrder = allowedContentType.SortOrder + }); } + // FIXME below, manage the property types - if (((ICanBeDirty)entity).IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty())) + // delete ??? fixme wtf is this?! + // by excepting entries from db with entries from collections + if (entity.IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty())) { - //Delete PropertyTypes by excepting entries from db with entries from collections var dbPropertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = entity.Id }); var dbPropertyTypeAlias = dbPropertyTypes.Select(x => x.Id); var entityPropertyTypes = entity.PropertyTypes.Where(x => x.HasIdentity).Select(x => x.Id); var items = dbPropertyTypeAlias.Except(entityPropertyTypes); foreach (var item in items) - { - //Before a PropertyType can be deleted, all Properties based on that PropertyType should be deleted. - Database.Delete("WHERE propertyTypeId = @Id", new { Id = item }); - Database.Delete("WHERE propertytypeid = @Id", new { Id = item }); - Database.Delete("WHERE contentTypeId = @Id AND id = @PropertyTypeId", - new { Id = entity.Id, PropertyTypeId = item }); - } + DeletePropertyType(entity.Id, item); } + // delete tabs + // by excepting entries from db with entries from collections + List orphanPropertyTypeIds = null; if (entity.IsPropertyDirty("PropertyGroups") || entity.PropertyGroups.Any(x => x.IsDirty())) { // todo @@ -357,68 +352,97 @@ AND umbracoNode.id <> @id", // (all gone) // delete tabs that do not exist anymore - // get the tabs that are currently existing (in the db) - // get the tabs that we want, now - // and derive the tabs that we want to delete + // get the tabs that are currently existing (in the db), get the tabs that we want, + // now, and derive the tabs that we want to delete var existingPropertyGroups = Database.Fetch("WHERE contentTypeNodeId = @id", new { id = entity.Id }) .Select(x => x.Id) .ToList(); var newPropertyGroups = entity.PropertyGroups.Select(x => x.Id).ToList(); - var tabsToDelete = existingPropertyGroups + var groupsToDelete = existingPropertyGroups .Except(newPropertyGroups) .ToArray(); - // move properties to generic properties, and delete the tabs - if (tabsToDelete.Length > 0) + // delete the tabs + if (groupsToDelete.Length > 0) { - Database.Update("SET propertyTypeGroupId=NULL WHERE propertyTypeGroupId IN (@ids)", new { ids = tabsToDelete }); - Database.Delete("WHERE id IN (@ids)", new { ids = tabsToDelete }); + // if the tab contains properties, take care of them + // - move them to 'generic properties' so they remain consistent + // - keep track of them, later on we'll figure out what to do with them + // see http://issues.umbraco.org/issue/U4-8663 + orphanPropertyTypeIds = Database.Fetch("WHERE propertyTypeGroupId IN (@ids)", new { ids = groupsToDelete }) + .Select(x => x.Id).ToList(); + Database.Update("SET propertyTypeGroupId=NULL WHERE propertyTypeGroupId IN (@ids)", new { ids = groupsToDelete }); + + // now we can delete the tabs + Database.Delete("WHERE id IN (@ids)", new { ids = groupsToDelete }); } } + var propertyGroupFactory = new PropertyGroupFactory(entity.Id); - //Run through all groups to insert or update entries + // insert or update groups, assign properties foreach (var propertyGroup in entity.PropertyGroups) { - var tabDto = propertyGroupFactory.BuildGroupDto(propertyGroup); - int groupPrimaryKey = propertyGroup.HasIdentity - ? Database.Update(tabDto) - : Convert.ToInt32(Database.Insert(tabDto)); + // insert or update group + var groupDto = propertyGroupFactory.BuildGroupDto(propertyGroup); + var groupId = propertyGroup.HasIdentity + ? Database.Update(groupDto) + : Convert.ToInt32(Database.Insert(groupDto)); if (propertyGroup.HasIdentity == false) - propertyGroup.Id = groupPrimaryKey; //Set Id on new PropertyGroup + propertyGroup.Id = groupId; + else + groupId = propertyGroup.Id; - //Ensure that the PropertyGroup's Id is set on the PropertyTypes within a group - //unless the PropertyGroupId has already been changed. + // assign properties to the group + // (all of them, even those that have .IsPropertyDirty("PropertyGroupId") == true, + // because it should have been set to this group anyways and better be safe) foreach (var propertyType in propertyGroup.PropertyTypes) - { - if (propertyType.IsPropertyDirty("PropertyGroupId") == false) - { - var tempGroup = propertyGroup; - propertyType.PropertyGroupId = new Lazy(() => tempGroup.Id); - } - } + propertyType.PropertyGroupId = new Lazy(() => groupId); } - //Run through all PropertyTypes to insert or update entries + // insert or update properties + // all of them, no-group and in groups foreach (var propertyType in entity.PropertyTypes) { - var tabId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int); - //If the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias - if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int)) - { - AssignDataTypeFromPropertyEditor(propertyType); - } + var groupId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int); - //validate the alias! + // if the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias + if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int)) + AssignDataTypeFromPropertyEditor(propertyType); + + // validate the alias ValidateAlias(propertyType); - var propertyTypeDto = propertyGroupFactory.BuildPropertyTypeDto(tabId, propertyType); - int typePrimaryKey = propertyType.HasIdentity - ? Database.Update(propertyTypeDto) - : Convert.ToInt32(Database.Insert(propertyTypeDto)); + // insert or update property + var propertyTypeDto = propertyGroupFactory.BuildPropertyTypeDto(groupId, propertyType); + var typeId = propertyType.HasIdentity + ? Database.Update(propertyTypeDto) + : Convert.ToInt32(Database.Insert(propertyTypeDto)); if (propertyType.HasIdentity == false) - propertyType.Id = typePrimaryKey; //Set Id on new PropertyType + propertyType.Id = typeId; + else + typeId = propertyType.Id; + + // not an orphan anymore + if (orphanPropertyTypeIds != null) + orphanPropertyTypeIds.Remove(typeId); } + + // deal with orphan properties: those that were in a deleted tab, + // and have not been re-mapped to another tab or to 'generic properties' + if (orphanPropertyTypeIds != null) + foreach (var id in orphanPropertyTypeIds) + DeletePropertyType(entity.Id, id); + } + + private void DeletePropertyType(int contentTypeId, int propertyTypeId) + { + // first clear dependencies + Database.Delete("WHERE propertyTypeId = @Id", new { Id = propertyTypeId }); + Database.Delete("WHERE propertytypeid = @Id", new { Id = propertyTypeId }); + + // then delete the property type + Database.Delete("WHERE contentTypeId = @Id AND id = @PropertyTypeId", new { Id = contentTypeId, PropertyTypeId = propertyTypeId }); } protected IEnumerable GetAllowedContentTypeIds(int id) @@ -649,7 +673,7 @@ AND umbracoNode.id <> @id", var allParentContentTypes = contentTypes.Where(x => allParentIdsAsArray.Contains(x.Id)).ToArray(); foreach (var contentType in contentTypes) - { + { var entityId = contentType.Id; var parentContentTypes = allParentContentTypes.Where(x => @@ -714,10 +738,10 @@ AND umbracoNode.id <> @id", out IDictionary> parentMediaTypeIds) { Mandate.ParameterNotNull(db, "db"); - + var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, - AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, + AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, ParentTypes.parentContentTypeId as chtParentId, ParentTypes.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, @@ -741,7 +765,7 @@ AND umbracoNode.id <> @id", ON ParentTypes.childContentTypeId = cmsContentType.nodeId WHERE (umbracoNode.nodeObjectType = @nodeObjectType) ORDER BY ctId"; - + var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.MediaType) }); if (result.Any() == false) @@ -848,16 +872,16 @@ AND umbracoNode.id <> @id", return mediaType; } - internal static IEnumerable MapContentTypes(Database db, ISqlSyntaxProvider sqlSyntax, + internal static IEnumerable MapContentTypes(Database db, ISqlSyntaxProvider sqlSyntax, out IDictionary> associatedTemplates, out IDictionary> parentContentTypeIds) { Mandate.ParameterNotNull(db, "db"); - + var sql = @"SELECT cmsDocumentType.IsDefault as dtIsDefault, cmsDocumentType.templateNodeId as dtTemplateId, cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, - AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, + AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, ParentTypes.parentContentTypeId as chtParentId,ParentTypes.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, @@ -890,7 +914,7 @@ AND umbracoNode.id <> @id", ON ParentTypes.childContentTypeId = cmsContentType.nodeId WHERE (umbracoNode.nodeObjectType = @nodeObjectType) ORDER BY ctId"; - + var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.DocumentType)}); if (result.Any() == false) @@ -911,7 +935,7 @@ AND umbracoNode.id <> @id", { var ct = queue.Dequeue(); - //check for default templates + //check for default templates bool? isDefaultTemplate = Convert.ToBoolean(ct.dtIsDefault); int? templateId = ct.dtTemplateId; if (currDefaultTemplate == -1 && isDefaultTemplate.HasValue && isDefaultTemplate.Value && templateId.HasValue) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs index 49d52ffffa..cc13275798 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false).Where(GetBaseWhereClause(), new { id = id, NodeObjectType = NodeObjectTypeId }); - var nodeDto = Database.Fetch(sql).FirstOrDefault(); + var nodeDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return nodeDto == null ? null : CreateEntity(nodeDto); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs index 276f4b0f89..7c80bd10b7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs @@ -58,12 +58,12 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var macroDto = Database.Fetch(sql).FirstOrDefault(); - if (macroDto == null) + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + if (dto == null) return null; var factory = new ExternalLoginFactory(); - var entity = factory.BuildEntity(macroDto); + var entity = factory.BuildEntity(dto); //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index ab176df6d0..e2555995d2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; +using System.Xml; using System.Xml.Linq; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -11,6 +12,12 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository { + /// + /// This builds the Xml document used for the XML cache + /// + /// + XmlDocument BuildXmlCache(); + /// /// Get the count of published items /// @@ -85,9 +92,9 @@ namespace Umbraco.Core.Persistence.Repositories /// Field to order by /// Direction to order by /// Flag to indicate when ordering by system field - /// Search text filter + /// /// An Enumerable list of objects IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs new file mode 100644 index 0000000000..adf0816196 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Defines the repository. + /// + public interface IRedirectUrlRepository : IRepositoryQueryable + { + /// + /// Gets a redirect url. + /// + /// The Umbraco redirect url route. + /// The content unique key. + /// + IRedirectUrl Get(string url, Guid contentKey); + + /// + /// Deletes a redirect url. + /// + /// The redirect url identifier. + void Delete(Guid id); + + /// + /// Deletes all redirect urls. + /// + void DeleteAll(); + + /// + /// Deletes all redirect urls for a given content. + /// + /// The content unique key. + void DeleteContentUrls(Guid contentKey); + + /// + /// Gets the most recent redirect url corresponding to an Umbraco redirect url route. + /// + /// The Umbraco redirect url route. + /// The most recent redirect url corresponding to the route. + IRedirectUrl GetMostRecentUrl(string url); + + /// + /// Gets all redirect urls for a content item. + /// + /// The content unique key. + /// All redirect urls for the content item. + IEnumerable GetContentUrls(Guid contentKey); + + /// + /// Gets all redirect urls. + /// + /// The page index. + /// The page size. + /// The total count of redirect urls. + /// The redirect urls. + IEnumerable GetAllUrls(long pageIndex, int pageSize, out long total); + + /// + /// Gets all redirect urls below a given content item. + /// + /// The content unique identifier. + /// The page index. + /// The page size. + /// The total count of redirect urls. + /// The redirect urls. + IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total); + + /// + /// Searches for all redirect urls that contain a given search term in their URL property. + /// + /// The term to search for. + /// The page index. + /// The page size. + /// The total count of redirect urls. + /// The redirect urls. + IEnumerable SearchUrls(string searchTerm, long pageIndex, int pageSize, out long total); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 54a8b4409f..97981849bc 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -52,9 +52,9 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate, SqlSyntax); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; @@ -453,8 +453,16 @@ namespace Umbraco.Core.Persistence.Repositories Func> filterCallback = null; if (filter.IsNullOrWhiteSpace() == false) { - sbWhere.Append("AND (umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ")"); + sbWhere + .Append("AND (") + .Append(SqlSyntax.GetQuotedTableName("umbracoNode")) + .Append(".") + .Append(SqlSyntax.GetQuotedColumnName("text")) + .Append(" LIKE @") + .Append(args.Count) + .Append(")"); args.Add("%" + filter + "%"); + filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index 40121223a2..2c0e910428 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -32,7 +32,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return dto == null ? null : _modelFactory.BuildEntity(dto); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index dfbcf61b7f..f14d66691b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -52,9 +52,9 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate, SqlSyntax); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; @@ -622,9 +622,32 @@ namespace Umbraco.Core.Persistence.Repositories Func> filterCallback = null; if (filter.IsNullOrWhiteSpace() == false) { - sbWhere.Append("AND ((umbracoNode. " + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ") " + - "OR (cmsMember.LoginName LIKE @0" + args.Count + "))"); - args.Add("%" + filter + "%"); + //This will build up the where clause - even though the same 'filter' is being + //applied to both columns, the parameters values passed to PetaPoco need to be + //duplicated, otherwise it gets confused :/ + var columnFilters = new List> + { + new Tuple("umbracoNode", "text"), + new Tuple("cmsMember", "LoginName") + }; + sbWhere.Append("AND ("); + for (int i = 0; i < columnFilters.Count; i++) + { + sbWhere + .Append("(") + .Append(SqlSyntax.GetQuotedTableName(columnFilters[i].Item1)) + .Append(".") + .Append(SqlSyntax.GetQuotedColumnName(columnFilters[i].Item2)) + .Append(" LIKE @") + .Append(args.Count) + .Append(") "); + args.Add(string.Format("%{0}%", filter)); + if (i < (columnFilters.Count - 1)) + { + sbWhere.Append("OR "); + } + } + sbWhere.Append(")"); filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index b35c793400..d06399a85b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -94,11 +94,11 @@ namespace Umbraco.Core.Persistence.Repositories } sql.Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.id AS PropertyTypeId", "cmsPropertyType.Alias", - "cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.mandatory", + "cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.mandatory", "cmsPropertyType.UniqueID", "cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", "cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", "cmsDataType.propertyEditorAlias", "cmsDataType.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId", - "cmsPropertyTypeGroup.text AS PropertyGroupName", + "cmsPropertyTypeGroup.text AS PropertyGroupName", "cmsPropertyTypeGroup.uniqueID AS PropertyGroupUniqueID", "cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder", "cmsPropertyTypeGroup.contenttypeNodeId") .From() .InnerJoin().On(left => left.NodeId, right => right.NodeId) diff --git a/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs index 19df777666..b4a21f5d21 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var dto = Database.First(sql); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs index 22fad9d99b..37200b2172 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs @@ -17,8 +17,7 @@ namespace Umbraco.Core.Persistence.Repositories { public PublicAccessRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) - { - } + { } private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; protected override IRepositoryCachePolicyFactory CachePolicyFactory @@ -46,6 +45,8 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("umbracoAccess.id IN (@ids)", new { ids = ids }); } + sql.OrderBy(x => x.NodeId, SqlSyntax); + var factory = new PublicAccessEntryFactory(); var dtos = Database.Fetch(new AccessRulesRelator().Map, sql); return dtos.Select(factory.BuildEntity); @@ -69,7 +70,7 @@ namespace Umbraco.Core.Persistence.Repositories .From(SqlSyntax) .LeftJoin(SqlSyntax) .On(SqlSyntax, left => left.Id, right => right.AccessId); - + return sql; } @@ -162,7 +163,5 @@ namespace Umbraco.Core.Persistence.Repositories { return entity.Key; } - - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs index be1f03a73f..f17a0dd0d2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs @@ -49,6 +49,10 @@ namespace Umbraco.Core.Persistence.Repositories INNER JOIN umbracoNode as TB2 ON TB1.nodeId = TB2.id WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)", FormatDeleteStatement("umbracoAccess", "nodeId"), + @"DELETE FROM umbracoRedirectUrl WHERE umbracoRedirectUrl.id IN( + SELECT TB1.id FROM umbracoRedirectUrl as TB1 + INNER JOIN umbracoNode as TB2 ON TB1.contentKey = TB2.uniqueId + WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)", FormatDeleteStatement("umbracoRelation", "parentId"), FormatDeleteStatement("umbracoRelation", "childId"), FormatDeleteStatement("cmsTagRelationship", "nodeId"), diff --git a/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs new file mode 100644 index 0000000000..5452e868a0 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/RedirectUrlRepository.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + internal class RedirectUrlRepository : PetaPocoRepositoryBase, IRedirectUrlRepository + { + public RedirectUrlRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) + { } + + protected override int PerformCount(IQuery query) + { + throw new NotSupportedException("This repository does not support this method."); + } + + protected override bool PerformExists(Guid id) + { + return PerformGet(id) != null; + } + + protected override IRedirectUrl PerformGet(Guid id) + { + var sql = GetBaseQuery(false).Where(x => x.Id == id); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + return dto == null ? null : Map(dto); + } + + protected override IEnumerable PerformGetAll(params Guid[] ids) + { + if (ids.Length > 2000) + throw new NotSupportedException("This repository does not support more than 2000 ids."); + var sql = GetBaseQuery(false).WhereIn(x => x.Id, ids); + var dtos = Database.Fetch(sql); + return dtos.WhereNotNull().Select(Map); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotSupportedException("This repository does not support this method."); + } + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = new Sql(); + if (isCount) + sql.Select(@"COUNT(*) +FROM umbracoRedirectUrl +JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); + else + sql.Select(@"umbracoRedirectUrl.*, umbracoNode.id AS contentId +FROM umbracoRedirectUrl +JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); + return sql; + } + + protected override string GetBaseWhereClause() + { + return "id = @Id"; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + "DELETE FROM umbracoRedirectUrl WHERE id = @Id" + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { throw new NotImplementedException(); } + } + + protected override void PersistNewItem(IRedirectUrl entity) + { + var dto = Map(entity); + Database.Insert(dto); + entity.Id = entity.Key.GetHashCode(); + } + + protected override void PersistUpdatedItem(IRedirectUrl entity) + { + var dto = Map(entity); + Database.Update(dto); + } + + private static RedirectUrlDto Map(IRedirectUrl redirectUrl) + { + if (redirectUrl == null) return null; + + return new RedirectUrlDto + { + Id = redirectUrl.Key, + ContentKey = redirectUrl.ContentKey, + CreateDateUtc = redirectUrl.CreateDateUtc, + Url = redirectUrl.Url, + UrlHash = redirectUrl.Url.ToSHA1() + }; + } + + private static IRedirectUrl Map(RedirectUrlDto dto) + { + if (dto == null) return null; + + var url = new RedirectUrl(); + try + { + url.DisableChangeTracking(); + url.Key = dto.Id; + url.Id = dto.Id.GetHashCode(); + url.ContentId = dto.ContentId; + url.ContentKey = dto.ContentKey; + url.CreateDateUtc = dto.CreateDateUtc; + url.Url = dto.Url; + return url; + } + finally + { + url.EnableChangeTracking(); + } + } + + public IRedirectUrl Get(string url, Guid contentKey) + { + var urlHash = url.ToSHA1(); + var sql = GetBaseQuery(false).Where(x => x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey); + var dto = Database.Fetch(sql).FirstOrDefault(); + return dto == null ? null : Map(dto); + } + + public void DeleteAll() + { + Database.Execute("DELETE FROM umbracoRedirectUrl"); + } + + public void DeleteContentUrls(Guid contentKey) + { + Database.Execute("DELETE FROM umbracoRedirectUrl WHERE contentKey=@contentKey", new { contentKey }); + } + + public void Delete(Guid id) + { + Database.Delete(id); + } + + public IRedirectUrl GetMostRecentUrl(string url) + { + var urlHash = url.ToSHA1(); + var sql = GetBaseQuery(false) + .Where(x => x.Url == url && x.UrlHash == urlHash) + .OrderByDescending(x => x.CreateDateUtc, SqlSyntax); + var dtos = Database.Fetch(sql); + var dto = dtos.FirstOrDefault(); + return dto == null ? null : Map(dto); + } + + public IEnumerable GetContentUrls(Guid contentKey) + { + var sql = GetBaseQuery(false) + .Where(x => x.ContentKey == contentKey) + .OrderByDescending(x => x.CreateDateUtc, SqlSyntax); + var dtos = Database.Fetch(sql); + return dtos.Select(Map); + } + + public IEnumerable GetAllUrls(long pageIndex, int pageSize, out long total) + { + var sql = GetBaseQuery(false) + .OrderByDescending(x => x.CreateDateUtc, SqlSyntax); + var result = Database.Page(pageIndex + 1, pageSize, sql); + total = Convert.ToInt32(result.TotalItems); + return result.Items.Select(Map); + } + + public IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total) + { + var sql = GetBaseQuery(false) + .Where(string.Format("{0}.{1} LIKE @path", SqlSyntax.GetQuotedTableName("umbracoNode"), SqlSyntax.GetQuotedColumnName("path")), new { path = "%," + rootContentId + ",%" }) + .OrderByDescending(x => x.CreateDateUtc, SqlSyntax); + var result = Database.Page(pageIndex + 1, pageSize, sql); + total = Convert.ToInt32(result.TotalItems); + + var rules = result.Items.Select(Map); + return rules; + } + + public IEnumerable SearchUrls(string searchTerm, long pageIndex, int pageSize, out long total) + { + var sql = GetBaseQuery(false) + .Where(string.Format("{0}.{1} LIKE @url", SqlSyntax.GetQuotedTableName("umbracoRedirectUrl"), SqlSyntax.GetQuotedColumnName("Url")), new { url = "%" + searchTerm.Trim().ToLowerInvariant() + "%" }) + .OrderByDescending(x => x.CreateDateUtc, SqlSyntax); + var result = Database.Page(pageIndex + 1, pageSize, sql); + total = Convert.ToInt32(result.TotalItems); + + var rules = result.Items.Select(Map); + return rules; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs index 69e0307c09..be0808bb19 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var dto = Database.FirstOrDefault(sql); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs index 228674d829..df0ae0b224 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var dto = Database.FirstOrDefault(sql); + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs index 20d3c38042..02794ef1fb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories protected override int PerformCount(IQuery query) { - throw new NotSupportedException("This repository does not support this method"); + throw new NotSupportedException("This repository does not support this method."); } protected override bool PerformExists(int id) @@ -55,7 +55,7 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable PerformGetByQuery(IQuery query) { - throw new NotSupportedException("This repository does not support this method"); + throw new NotSupportedException("This repository does not support this method."); } protected override Sql GetBaseQuery(bool isCount) diff --git a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs index 8b76330dbe..68f5496101 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -26,7 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var tagDto = Database.Fetch(sql).FirstOrDefault(); + var tagDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (tagDto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs index f30f945851..c02362f8fd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var taskDto = Database.Fetch(sql).FirstOrDefault(); + var taskDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (taskDto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs index 564e949172..a970880669 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var taskDto = Database.Fetch(sql).FirstOrDefault(); + var taskDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (taskDto == null) return null; diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 013cc2a442..45f3315645 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -236,18 +236,34 @@ namespace Umbraco.Core.Persistence.Repositories private Sql GetFilteredSqlForPagedResults(Sql sql, Func> defaultFilter = null) { - //copy to var so that the original isn't changed - var filteredSql = new Sql(sql.SQL, sql.Arguments); + Sql filteredSql; + // Apply filter if (defaultFilter != null) { var filterResult = defaultFilter(); - filteredSql.Append(filterResult.Item1, filterResult.Item2); + + //NOTE: this is certainly strange - NPoco handles this much better but we need to re-create the sql + // instance a couple of times to get the parameter order correct, for some reason the first + // time the arguments don't show up correctly but the SQL argument parameter names are actually updated + // accordingly - so we re-create it again. In v8 we don't need to do this and it's already taken care of. + + filteredSql = new Sql(sql.SQL, sql.Arguments); + var args = filteredSql.Arguments.Concat(filterResult.Item2).ToArray(); + filteredSql = new Sql( + string.Format("{0} {1}", filteredSql.SQL, filterResult.Item1), + args); + filteredSql = new Sql(filteredSql.SQL, args); + } + else + { + //copy to var so that the original isn't changed + filteredSql = new Sql(sql.SQL, sql.Arguments); } return filteredSql; } - private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy, bool orderBySystemField) + private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy, bool orderBySystemField, Tuple nodeIdSelect) { //copy to var so that the original isn't changed @@ -271,44 +287,71 @@ namespace Umbraco.Core.Persistence.Repositories } else { - // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through valie + // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through value // from most recent content version for the given order by field var sortedInt = string.Format(SqlSyntax.ConvertIntegerToOrderableString, "dataInt"); var sortedDate = string.Format(SqlSyntax.ConvertDateToOrderableString, "dataDate"); var sortedString = string.Format("COALESCE({0},'')", "dataNvarchar"); var sortedDecimal = string.Format(SqlSyntax.ConvertDecimalToOrderableString, "dataDecimal"); - var innerJoinTempTable = string.Format(@"INNER JOIN ( - SELECT CASE - WHEN dataInt Is Not Null THEN {0} - WHEN dataDecimal Is Not Null THEN {1} - WHEN dataDate Is Not Null THEN {2} - ELSE {3} - END AS CustomPropVal, - cd.nodeId AS CustomPropValContentId - FROM cmsDocument cd - INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = cd.nodeId AND cpd.versionId = cd.versionId - INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId - WHERE cpt.Alias = @{4} AND cd.newest = 1) AS CustomPropData - ON CustomPropData.CustomPropValContentId = umbracoNode.id - ", sortedInt, sortedDecimal, sortedDate, sortedString, sortedSql.Arguments.Length); + //these are defaults that will be used in the query - they can be overridden for non-versioned entities or document entities + var versionQuery = " AND cpd.versionId = cd.versionId"; + var newestQuery = string.Empty; + + //cmsDocument needs to filter by the 'newest' parameter in the query + if (nodeIdSelect.Item1 == "cmsDocument") + newestQuery = " AND cd.newest = 1"; + + //members do not use versions so clear the versionQuery string + if (nodeIdSelect.Item1 == "cmsMember") + versionQuery = string.Empty; + + //needs to be an outer join since there's no guarantee that any of the nodes have values for this property + var outerJoinTempTable = string.Format(@"LEFT OUTER JOIN ( + SELECT CASE + WHEN dataInt Is Not Null THEN {0} + WHEN dataDecimal Is Not Null THEN {1} + WHEN dataDate Is Not Null THEN {2} + ELSE {3} + END AS CustomPropVal, + cd.{4} AS CustomPropValContentId + FROM {5} cd + INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = cd.{4}{6} + INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId + WHERE cpt.Alias = @{7}{8}) AS CustomPropData + ON CustomPropData.CustomPropValContentId = umbracoNode.id + ", sortedInt, sortedDecimal, sortedDate, sortedString, nodeIdSelect.Item2, nodeIdSelect.Item1, versionQuery, sortedSql.Arguments.Length, newestQuery); + + //insert this just above the first LEFT OUTER JOIN (for cmsDocument) or the last WHERE (everything else) + string newSql; + if (nodeIdSelect.Item1 == "cmsDocument") + newSql = sortedSql.SQL.Insert(sortedSql.SQL.IndexOf("LEFT OUTER JOIN"), outerJoinTempTable); + else + newSql = sortedSql.SQL.Insert(sortedSql.SQL.LastIndexOf("WHERE"), outerJoinTempTable); - //insert this just above the LEFT OUTER JOIN - var newSql = sortedSql.SQL.Insert(sortedSql.SQL.IndexOf("LEFT OUTER JOIN"), innerJoinTempTable); var newArgs = sortedSql.Arguments.ToList(); newArgs.Add(orderBy); sortedSql = new Sql(newSql, newArgs.ToArray()); - sortedSql.OrderBy("CustomPropData.CustomPropVal"); if (orderDirection == Direction.Descending) { - sortedSql.Append(" DESC"); - } + sortedSql.OrderByDescending("CustomPropData.CustomPropVal"); + } + else + { + sortedSql.OrderBy("CustomPropData.CustomPropVal"); + } } + //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 + sortedSql.OrderBy("umbracoNode.id"); + return sortedSql; - + } /// @@ -356,7 +399,7 @@ namespace Umbraco.Core.Persistence.Repositories //get sorted and filtered sql var sqlNodeIdsWithSort = GetSortedSqlForPagedResults( GetFilteredSqlForPagedResults(sqlNodeIds, defaultFilter), - orderDirection, orderBy, orderBySystemField); + orderDirection, orderBy, orderBySystemField, nodeIdSelect); // Get page of results and total count IEnumerable result; @@ -394,7 +437,7 @@ namespace Umbraco.Core.Persistence.Repositories //get sorted and filtered sql var fullQuery = GetSortedSqlForPagedResults( GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter), - orderDirection, orderBy, orderBySystemField); + orderDirection, orderBy, orderBySystemField, nodeIdSelect); return processQuery(fullQuery); } else @@ -468,17 +511,6 @@ WHERE EXISTS( var propertyFactory = new PropertyFactory(compositionProperties, def.Version, def.Id, def.CreateDate, def.VersionDate); var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray(); - var newProperties = properties.Where(x => x.HasIdentity == false && x.PropertyType.HasIdentity); - - foreach (var property in newProperties) - { - var propertyDataDto = new PropertyDataDto { NodeId = def.Id, PropertyTypeId = property.PropertyTypeId, VersionId = def.Version }; - int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - - property.Version = def.Version; - property.Id = primaryKey; - } - foreach (var property in properties) { //NOTE: The benchmarks run with and without the following code show very little change so this is not a perf bottleneck diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index e2b2c2cc22..85eb00ea87 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -333,5 +333,14 @@ namespace Umbraco.Core.Persistence _logger, _sqlSyntax, containerObjectType); } + + public IRedirectUrlRepository CreateRedirectUrlRepository(IDatabaseUnitOfWork uow) + { + return new RedirectUrlRepository( + uow, + _cacheHelper, + _logger, + _sqlSyntax); + } } } \ 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 e61b23c8ec..366376b953 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; @@ -66,6 +67,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax string Format(ForeignKeyDefinition foreignKey); string FormatColumnRename(string tableName, string oldName, string newName); string FormatTableRename(string oldName, string newName); + Sql SelectTop(Sql sql, int top); bool SupportsClustered(); bool SupportsIdentityInsert(); bool? SupportsCaseInsensitiveQueries(Database db); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs index a6d1d690ba..449f5fb3b1 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs @@ -29,7 +29,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax public override string GetQuotedTableName(string tableName) { - return string.Format("[{0}]", tableName); + if (tableName.Contains(".") == false) + return string.Format("[{0}]", tableName); + + var tableNameParts = tableName.Split(new[] { '.' }, 2); + return string.Format("[{0}].[{1}]", tableNameParts[0], tableNameParts[1]); } public override string GetQuotedColumnName(string columnName) diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index 7167a5af95..ceb8e9c83f 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// /// Represents an SqlSyntaxProvider for MySql /// - [SqlSyntaxProviderAttribute("MySql.Data.MySqlClient")] + [SqlSyntaxProvider(Constants.DatabaseProviders.MySql)] public class MySqlSyntaxProvider : SqlSyntaxProviderBase { private readonly ILogger _logger; @@ -178,6 +178,11 @@ ORDER BY TABLE_NAME, INDEX_NAME", return result > 0; } + public override Sql SelectTop(Sql sql, int top) + { + return new Sql(string.Concat(sql.SQL, " LIMIT ", top), sql.Arguments); + } + public override bool SupportsClustered() { return true; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 23e0a1bb87..69ebe11a14 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// /// Represents an SqlSyntaxProvider for Sql Ce /// - [SqlSyntaxProviderAttribute("System.Data.SqlServerCe.4.0")] + [SqlSyntaxProviderAttribute(Constants.DatabaseProviders.SqlCe)] public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase { public SqlCeSyntaxProvider() @@ -18,6 +18,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax } + public override Sql SelectTop(Sql sql, int top) + { + return new Sql(sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments); + } + public override bool SupportsClustered() { return false; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 9cb0ce327d..4c35c90e1b 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -8,18 +8,63 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// /// Represents an SqlSyntaxProvider for Sql Server /// - [SqlSyntaxProviderAttribute("System.Data.SqlClient")] + [SqlSyntaxProviderAttribute(Constants.DatabaseProviders.SqlServer)] public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase { public SqlServerSyntaxProvider() { - } + } /// /// Gets/sets the version of the current SQL server instance /// - internal Lazy VersionName { get; set; } + internal SqlServerVersionName GetVersionName(Database database) + { + if (_versionName.HasValue) + return _versionName.Value; + + try + { + var version = database.ExecuteScalar("SELECT SERVERPROPERTY('productversion')"); + var firstPart = version.Split('.')[0]; + switch (firstPart) + { + case "13": + _versionName = SqlServerVersionName.V2014; + break; + case "12": + _versionName = SqlServerVersionName.V2014; + break; + case "11": + _versionName = SqlServerVersionName.V2012; + break; + case "10": + _versionName = SqlServerVersionName.V2008; + break; + case "9": + _versionName = SqlServerVersionName.V2005; + break; + case "8": + _versionName = SqlServerVersionName.V2000; + break; + case "7": + _versionName = SqlServerVersionName.V7; + break; + default: + _versionName = SqlServerVersionName.Other; + break; + } + } + catch (Exception) + { + _versionName = SqlServerVersionName.Invalid; + } + + return _versionName.Value; + } + + private SqlServerVersionName? _versionName; /// /// SQL Server stores default values assigned to columns as constraints, it also stores them with named values, this is the only @@ -104,6 +149,11 @@ order by T.name, I.name"); return column.IsIdentity ? GetIdentityString(column) : string.Empty; } + public override Sql SelectTop(Sql sql, int top) + { + return new Sql(sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments); + } + private static string GetIdentityString(ColumnDefinition column) { return "IDENTITY(1,1)"; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs index 37870a9536..2f50f39435 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs @@ -3,6 +3,9 @@ /// /// Represents the version name of SQL server (i.e. the year 2008, 2005, etc...) /// + /// + /// see: https://support.microsoft.com/en-us/kb/321185 + /// internal enum SqlServerVersionName { Invalid = -1, @@ -11,6 +14,8 @@ V2005 = 2, V2008 = 3, V2012 = 4, - Other = 5 + V2014 = 5, + V2016 = 6, + Other = 100 } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 50417761aa..a2baa8132d 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -503,6 +503,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax protected abstract string FormatIdentity(ColumnDefinition column); + public abstract Sql SelectTop(Sql sql, int top); + public virtual string DeleteDefaultConstraint { get diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs index af18d9c3d9..1231765f20 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs @@ -22,12 +22,23 @@ /// /// See: http://issues.umbraco.org/issue/U4-3876 /// - public static Sql GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery) + public static Sql GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery, WhereInType whereInType = WhereInType.In) { - return new Sql(string.Format(@"DELETE FROM {0} WHERE {1} IN (SELECT {1} FROM ({2}) x)", + + return + new Sql(string.Format( + whereInType == WhereInType.In + ? @"DELETE FROM {0} WHERE {1} IN (SELECT {1} FROM ({2}) x)" + : @"DELETE FROM {0} WHERE {1} NOT IN (SELECT {1} FROM ({2}) x)", sqlProvider.GetQuotedTableName(tableName), sqlProvider.GetQuotedColumnName(columnName), subQuery.SQL), subQuery.Arguments); } } + + internal enum WhereInType + { + In, + NotIn + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index a0490348f3..5ca30f2860 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using StackExchange.Profiling; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence { @@ -154,5 +155,41 @@ namespace Umbraco.Core.Persistence } base.OnExecutedCommand(cmd); } + + /// + /// We are overriding this in the case that we are using SQL Server 2012+ so we can make paging more efficient than the default PetaPoco paging + /// see: http://issues.umbraco.org/issue/U4-8837 + /// + /// + /// + /// + /// + /// + /// + /// + /// + internal override void BuildSqlDbSpecificPagingQuery(DBType databaseType, long skip, long take, string sql, string sqlSelectRemoved, string sqlOrderBy, ref object[] args, out string sqlPage) + { + if (databaseType == DBType.SqlServer) + { + //we need to check it's version to see what kind of paging format we can use + //TODO: This is a hack, but we don't have access to the SqlSyntaxProvider here, we can in v8 but not now otherwise + // this would be a breaking change. + var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider; + if (sqlServerSyntax != null) + { + if ((int) sqlServerSyntax.GetVersionName(this) >= (int) SqlServerVersionName.V2012) + { + //we can use the good paging! to do that we are going to change the databaseType to SQLCE since + //it also uses the good paging syntax. + base.BuildSqlDbSpecificPagingQuery(DBType.SqlServerCE, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage); + return; + } + } + } + + //use the defaults + base.BuildSqlDbSpecificPagingQuery(databaseType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs index 2f225f1764..d120753185 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.PropertyEditors EditorView = editorView; //defaults - ValueType = "string"; + ValueType = PropertyEditorValueTypes.String; Icon = Constants.Icons.PropertyEditor; Group = "common"; } @@ -33,7 +33,7 @@ namespace Umbraco.Core.PropertyEditors Name = name; //defaults - ValueType = "string"; + ValueType = PropertyEditorValueTypes.String; Icon = Constants.Icons.PropertyEditor; Group = "common"; } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorValueTypes.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorValueTypes.cs new file mode 100644 index 0000000000..0ece42d82b --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorValueTypes.cs @@ -0,0 +1,34 @@ +using System; + +namespace Umbraco.Core.PropertyEditors +{ + public static class PropertyEditorValueTypes + { + // mapped to DataTypeDatabaseType in DataTypeService.OverrideDatabaseTypeIfProvidedInPreValues + // BUT what about those that are not mapped? + // + // also mapped to DataTypeDatabaseType in PropertyValueEditor + // and this time the "+" are mapped + + public const string Date = "DATE"; // +Date + + public const string DateTime = "DATETIME"; // Date + + public const string Decimal = "DECIMAL"; // Decimal + + public const string Integer = "INT"; // Integer + + [Obsolete("Use Integer.", false)] + public const string IntegerAlternative = "INTEGER"; // +Integer + + public const string Json = "JSON"; // +NText + + public const string Text = "TEXT"; // NText + + public const string Time = "TIME"; // +Date + + public const string String = "STRING"; // NVarchar + + public const string Xml = "XML"; // +NText + } +} diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs index af3a560837..0b083025f7 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.PropertyEditors /// public PropertyValueEditor() { - ValueType = "string"; + ValueType = PropertyEditorValueTypes.String; //set a default for validators Validators = new List(); } @@ -123,30 +123,35 @@ namespace Umbraco.Core.PropertyEditors } /// - /// Returns the true DataTypeDatabaseType from the string representation ValueType + /// Returns the true DataTypeDatabaseType from the string representation ValueType. /// /// public DataTypeDatabaseType GetDatabaseType() { - switch (ValueType.ToUpper(CultureInfo.InvariantCulture)) + return GetDatabaseType(ValueType); + } + + public static DataTypeDatabaseType GetDatabaseType(string valueType) + { + switch (valueType.ToUpperInvariant()) { - case "INT": - case "INTEGER": + case PropertyEditorValueTypes.Integer: + case PropertyEditorValueTypes.IntegerAlternative: return DataTypeDatabaseType.Integer; - case "DECIMAL": + case PropertyEditorValueTypes.Decimal: return DataTypeDatabaseType.Decimal; - case "STRING": + case PropertyEditorValueTypes.String: return DataTypeDatabaseType.Nvarchar; - case "TEXT": - case "JSON": - case "XML": + case PropertyEditorValueTypes.Text: + case PropertyEditorValueTypes.Json: + case PropertyEditorValueTypes.Xml: return DataTypeDatabaseType.Ntext; - case "DATETIME": - case "DATE": - case "TIME": + case PropertyEditorValueTypes.DateTime: + case PropertyEditorValueTypes.Date: + case PropertyEditorValueTypes.Time: return DataTypeDatabaseType.Date; default: - throw new FormatException("The ValueType does not match a known value type"); + throw new ArgumentException("Not a valid value type.", "valueType"); } } @@ -238,7 +243,7 @@ namespace Umbraco.Core.PropertyEditors public virtual object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) { //if it's json but it's empty json, then return null - if (ValueType.InvariantEquals("JSON") && editorValue.Value != null && editorValue.Value.ToString().DetectIsEmptyJson()) + if (ValueType.InvariantEquals(PropertyEditorValueTypes.Json) && editorValue.Value != null && editorValue.Value.ToString().DetectIsEmptyJson()) { return null; } diff --git a/src/Umbraco.Core/PropertyEditors/RequiredManifestValueValidator.cs b/src/Umbraco.Core/PropertyEditors/RequiredManifestValueValidator.cs index 33c2c02cb6..b3e8ff1f6f 100644 --- a/src/Umbraco.Core/PropertyEditors/RequiredManifestValueValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/RequiredManifestValueValidator.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.PropertyEditors { var asString = value.ToString(); - if (editor.ValueEditor.ValueType.InvariantEquals("JSON")) + if (editor.ValueEditor.ValueType.InvariantEquals(PropertyEditorValueTypes.Json)) { if (asString.DetectIsEmptyJson()) { diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs index 32fe356764..e32ff7af02 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Models.PublishedContent; +using System.Globalization; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.PropertyEditors.ValueConverters { @@ -20,7 +21,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters if (sourceString != null) { decimal d; - return (decimal.TryParse(sourceString, out d)) ? d : 0M; + return (decimal.TryParse(sourceString, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out d)) ? d : 0M; } // in the database an a decimal is an a decimal diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs index e5ed8b8b94..c5f0236a1a 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters { var propertyEditor = PropertyEditorResolver.Current.GetByAlias(propertyType.PropertyEditorAlias); if (propertyEditor == null) return false; - return propertyEditor.ValueEditor.ValueType.InvariantEquals("json"); + return propertyEditor.ValueEditor.ValueType.InvariantEquals(PropertyEditorValueTypes.Json); } public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs index 6725257888..a3b12a6688 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs @@ -28,13 +28,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters // // - var sourceString = source.ToString(); + var sourceString = source != null ? source.ToString() : null; if (string.IsNullOrWhiteSpace(sourceString)) return Enumerable.Empty(); //SD: I have no idea why this logic is here, I'm pretty sure we've never saved the multiple txt string // as xml in the database, it's always been new line delimited. Will ask Stephen about this. // In the meantime, we'll do this xml check, see if it parses and if not just continue with // splitting by newline + // + // RS: SD/Stephan Please consider post before deciding to remove + //// https://our.umbraco.org/forum/contributing-to-umbraco-cms/76989-keep-the-xml-values-in-the-multipletextstringvalueconverter var values = new List(); var pos = sourceString.IndexOf("", StringComparison.Ordinal); while (pos >= 0) diff --git a/src/Umbraco.Core/Publishing/ScheduledPublisher.cs b/src/Umbraco.Core/Publishing/ScheduledPublisher.cs index 45492423ab..94df1b88f1 100644 --- a/src/Umbraco.Core/Publishing/ScheduledPublisher.cs +++ b/src/Umbraco.Core/Publishing/ScheduledPublisher.cs @@ -17,8 +17,15 @@ namespace Umbraco.Core.Publishing _contentService = contentService; } - public void CheckPendingAndProcess() + /// + /// Processes scheduled operations + /// + /// + /// Returns the number of items successfully completed + /// + public int CheckPendingAndProcess() { + var counter = 0; foreach (var d in _contentService.GetContentForRelease()) { try @@ -32,10 +39,14 @@ namespace Umbraco.Core.Publishing LogHelper.Error("Could not published the document (" + d.Id + ") based on it's scheduled release, status result: " + result.Result.StatusType, result.Exception); } else - { + { LogHelper.Warn("Could not published the document (" + d.Id + ") based on it's scheduled release. Status result: " + result.Result.StatusType); } } + else + { + counter++; + } } catch (Exception ee) { @@ -48,7 +59,11 @@ namespace Umbraco.Core.Publishing try { d.ExpireDate = null; - _contentService.UnPublish(d, (int)d.GetWriterProfile().Id); + var result = _contentService.UnPublish(d, (int)d.GetWriterProfile().Id); + if (result) + { + counter++; + } } catch (Exception ee) { @@ -56,6 +71,8 @@ namespace Umbraco.Core.Publishing throw; } } + + return counter; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs b/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs new file mode 100644 index 0000000000..819fa87a56 --- /dev/null +++ b/src/Umbraco.Core/Security/ActiveDirectoryBackOfficeUserPasswordChecker.cs @@ -0,0 +1,31 @@ +using System.Configuration; +using System.DirectoryServices.AccountManagement; +using System.Threading.Tasks; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Core.Security +{ + public class ActiveDirectoryBackOfficeUserPasswordChecker : IBackOfficeUserPasswordChecker + { + public virtual string ActiveDirectoryDomain { + get { + return ConfigurationManager.AppSettings["ActiveDirectoryDomain"]; + } + } + + public Task CheckPasswordAsync(BackOfficeIdentityUser user, string password) + { + bool isValid; + using (var pc = new PrincipalContext(ContextType.Domain, ActiveDirectoryDomain)) + { + isValid = pc.ValidateCredentials(user.UserName, password); + } + + var result = isValid + ? BackOfficeUserPasswordCheckerResult.ValidCredentials + : BackOfficeUserPasswordCheckerResult.InvalidCredentials; + + return Task.FromResult(result); + } + } +} diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index 524663c9c5..9007c45946 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -307,11 +307,9 @@ namespace Umbraco.Core.Security } catch (Exception) { - //TODO: Do we need to do more here?? need to make sure that the forms cookie is gone, but is that - // taken care of in our custom middleware somehow? ctx.Authentication.SignOut( - Core.Constants.Security.BackOfficeAuthenticationType, - Core.Constants.Security.BackOfficeExternalAuthenticationType); + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeExternalAuthenticationType); return null; } } diff --git a/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs b/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs index 9c46ae69f4..7c2373a000 100644 --- a/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs +++ b/src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs @@ -7,7 +7,8 @@ using Umbraco.Core.Models.Identity; namespace Umbraco.Core.Security { - public class BackOfficeClaimsIdentityFactory : ClaimsIdentityFactory + public class BackOfficeClaimsIdentityFactory : ClaimsIdentityFactory + where T: BackOfficeIdentityUser { public BackOfficeClaimsIdentityFactory() { @@ -20,7 +21,7 @@ namespace Umbraco.Core.Security /// /// /// - public override async Task CreateAsync(UserManager manager, BackOfficeIdentityUser user, string authenticationType) + public override async Task CreateAsync(UserManager manager, T user, string authenticationType) { var baseIdentity = await base.CreateAsync(manager, user, authenticationType); @@ -42,4 +43,9 @@ namespace Umbraco.Core.Security return umbracoIdentity; } } + + public class BackOfficeClaimsIdentityFactory : BackOfficeClaimsIdentityFactory + { + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs index f1d18b9d0f..7c65b43291 100644 --- a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Security private readonly ILogger _logger; private readonly IOwinRequest _request; - public BackOfficeSignInManager(BackOfficeUserManager userManager, IAuthenticationManager authenticationManager, ILogger logger, IOwinRequest request) + public BackOfficeSignInManager(UserManager userManager, IAuthenticationManager authenticationManager, ILogger logger, IOwinRequest request) : base(userManager, authenticationManager) { if (logger == null) throw new ArgumentNullException("logger"); @@ -29,13 +29,13 @@ namespace Umbraco.Core.Security public override Task CreateUserIdentityAsync(BackOfficeIdentityUser user) { - return user.GenerateUserIdentityAsync((BackOfficeUserManager)UserManager); + return user.GenerateUserIdentityAsync((BackOfficeUserManager)UserManager); } public static BackOfficeSignInManager Create(IdentityFactoryOptions options, IOwinContext context, ILogger logger) { return new BackOfficeSignInManager( - context.GetUserManager(), + context.GetBackOfficeUserManager(), context.Authentication, logger, context.Request); @@ -46,7 +46,7 @@ namespace Umbraco.Core.Security /// /// /// - public async override Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout) + public override async Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout) { var result = await base.PasswordSignInAsync(userName, password, isPersistent, shouldLockout); diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs index e48079c10b..2b68fd07eb 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using System.Web.Security; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; +using Microsoft.Owin.Security.DataProtection; using Umbraco.Core.Models.Identity; using Umbraco.Core.Services; @@ -15,6 +16,8 @@ namespace Umbraco.Core.Security /// public class BackOfficeUserManager : BackOfficeUserManager { + public const string OwinMarkerKey = "Umbraco.Web.Security.Identity.BackOfficeUserManagerMarker"; + public BackOfficeUserManager(IUserStore store) : base(store) { @@ -26,9 +29,8 @@ namespace Umbraco.Core.Security MembershipProviderBase membershipProvider) : base(store) { - if (options == null) throw new ArgumentNullException("options"); - var manager = new BackOfficeUserManager(store); - InitUserManager(manager, membershipProvider, options); + if (options == null) throw new ArgumentNullException("options");; + InitUserManager(this, membershipProvider, options); } #region Static Create methods @@ -69,7 +71,7 @@ namespace Umbraco.Core.Security { var manager = new BackOfficeUserManager(customUserStore, options, membershipProvider); return manager; - } + } #endregion /// @@ -79,65 +81,15 @@ namespace Umbraco.Core.Security /// /// /// - protected void InitUserManager(BackOfficeUserManager manager, MembershipProviderBase membershipProvider, IdentityFactoryOptions options) + protected void InitUserManager( + BackOfficeUserManager manager, + MembershipProviderBase membershipProvider, + IdentityFactoryOptions options) { - // Configure validation logic for usernames - manager.UserValidator = new UserValidator(manager) - { - AllowOnlyAlphanumericUserNames = false, - RequireUniqueEmail = true - }; - - // Configure validation logic for passwords - manager.PasswordValidator = new PasswordValidator - { - RequiredLength = membershipProvider.MinRequiredPasswordLength, - RequireNonLetterOrDigit = membershipProvider.MinRequiredNonAlphanumericCharacters > 0, - RequireDigit = false, - RequireLowercase = false, - RequireUppercase = false - //TODO: Do we support the old regex match thing that membership providers used? - }; - - //use a custom hasher based on our membership provider - manager.PasswordHasher = new MembershipPasswordHasher(membershipProvider); - - var dataProtectionProvider = options.DataProtectionProvider; - if (dataProtectionProvider != null) - { - manager.UserTokenProvider = new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")); - } - - manager.UserLockoutEnabledByDefault = true; - manager.MaxFailedAccessAttemptsBeforeLockout = membershipProvider.MaxInvalidPasswordAttempts; - //NOTE: This just needs to be in the future, we currently don't support a lockout timespan, it's either they are locked - // or they are not locked, but this determines what is set on the account lockout date which corresponds to whether they are - // locked out or not. - manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromDays(30); - - //custom identity factory for creating the identity object for which we auth against in the back office - manager.ClaimsIdentityFactory = new BackOfficeClaimsIdentityFactory(); - - manager.EmailService = new EmailService(); - - //NOTE: Not implementing these, if people need custom 2 factor auth, they'll need to implement their own UserStore to suport it - - //// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user - //// You can write your own provider and plug in here. - //manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider - //{ - // MessageFormat = "Your security code is: {0}" - //}); - //manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider - //{ - // Subject = "Security Code", - // BodyFormat = "Your security code is: {0}" - //}); - - //manager.SmsService = new SmsService(); + //NOTE: This method is mostly here for backwards compat + base.InitUserManager(manager, membershipProvider, options.DataProtectionProvider); } - } /// @@ -180,6 +132,73 @@ namespace Umbraco.Core.Security } #endregion + /// + /// Initializes the user manager with the correct options + /// + /// + /// + /// + /// + protected void InitUserManager( + BackOfficeUserManager manager, + MembershipProviderBase membershipProvider, + IDataProtectionProvider dataProtectionProvider) + { + // Configure validation logic for usernames + manager.UserValidator = new UserValidator(manager) + { + AllowOnlyAlphanumericUserNames = false, + RequireUniqueEmail = true + }; + + // Configure validation logic for passwords + manager.PasswordValidator = new PasswordValidator + { + RequiredLength = membershipProvider.MinRequiredPasswordLength, + RequireNonLetterOrDigit = membershipProvider.MinRequiredNonAlphanumericCharacters > 0, + RequireDigit = false, + RequireLowercase = false, + RequireUppercase = false + //TODO: Do we support the old regex match thing that membership providers used? + }; + + //use a custom hasher based on our membership provider + manager.PasswordHasher = new MembershipPasswordHasher(membershipProvider); + + if (dataProtectionProvider != null) + { + manager.UserTokenProvider = new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")); + } + + manager.UserLockoutEnabledByDefault = true; + manager.MaxFailedAccessAttemptsBeforeLockout = membershipProvider.MaxInvalidPasswordAttempts; + //NOTE: This just needs to be in the future, we currently don't support a lockout timespan, it's either they are locked + // or they are not locked, but this determines what is set on the account lockout date which corresponds to whether they are + // locked out or not. + manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromDays(30); + + //custom identity factory for creating the identity object for which we auth against in the back office + manager.ClaimsIdentityFactory = new BackOfficeClaimsIdentityFactory(); + + manager.EmailService = new EmailService(); + + //NOTE: Not implementing these, if people need custom 2 factor auth, they'll need to implement their own UserStore to suport it + + //// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user + //// You can write your own provider and plug in here. + //manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider + //{ + // MessageFormat = "Your security code is: {0}" + //}); + //manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider + //{ + // Subject = "Security Code", + // BodyFormat = "Your security code is: {0}" + //}); + + //manager.SmsService = new SmsService(); + } + /// /// Logic used to validate a username and password /// @@ -200,7 +219,7 @@ namespace Umbraco.Core.Security /// We've allowed this check to be overridden with a simple callback so that developers don't actually /// have to implement/override this class. /// - public async override Task CheckPasswordAsync(T user, string password) + public override async Task CheckPasswordAsync(T user, string password) { if (BackOfficeUserPasswordChecker != null) { diff --git a/src/Umbraco.Core/Security/BackOfficeUserManagerMarker.cs b/src/Umbraco.Core/Security/BackOfficeUserManagerMarker.cs new file mode 100644 index 0000000000..9dd5afd9da --- /dev/null +++ b/src/Umbraco.Core/Security/BackOfficeUserManagerMarker.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.AspNet.Identity.Owin; +using Microsoft.Owin; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Core.Security +{ + /// + /// This class is only here due to the fact that IOwinContext Get / Set only work in generics, if they worked + /// with regular 'object' then we wouldn't have to use this work around but because of that we have to use this + /// class to resolve the 'real' type of the registered user manager + /// + /// + /// + internal class BackOfficeUserManagerMarker : IBackOfficeUserManagerMarker + where TManager : BackOfficeUserManager + where TUser : BackOfficeIdentityUser + { + public BackOfficeUserManager GetManager(IOwinContext owin) + { + var mgr = owin.Get() as BackOfficeUserManager; + if (mgr == null) throw new InvalidOperationException("Could not cast the registered back office user of type " + typeof(TManager) + " to " + typeof(BackOfficeUserManager)); + return mgr; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Security/EmailService.cs b/src/Umbraco.Core/Security/EmailService.cs index 93864aa37c..807d528f3d 100644 --- a/src/Umbraco.Core/Security/EmailService.cs +++ b/src/Umbraco.Core/Security/EmailService.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Security //TODO: This check could be nicer but that is the way it is currently mailMessage.IsBodyHtml = message.Body.IsNullOrWhiteSpace() == false - && message.Body.Contains("<") && message.Body.Contains("/>"); + && message.Body.Contains("<") && message.Body.Contains(" + /// This interface is only here due to the fact that IOwinContext Get / Set only work in generics, if they worked + /// with regular 'object' then we wouldn't have to use this work around but because of that we have to use this + /// class to resolve the 'real' type of the registered user manager + /// + internal interface IBackOfficeUserManagerMarker + { + BackOfficeUserManager GetManager(IOwinContext owin); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Security/OwinExtensions.cs b/src/Umbraco.Core/Security/OwinExtensions.cs new file mode 100644 index 0000000000..251f008a8c --- /dev/null +++ b/src/Umbraco.Core/Security/OwinExtensions.cs @@ -0,0 +1,47 @@ +using System; +using Microsoft.AspNet.Identity.Owin; +using Microsoft.Owin; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Core.Security +{ + public static class OwinExtensions + { + /// + /// Gets the back office sign in manager out of OWIN + /// + /// + /// + public static BackOfficeSignInManager GetBackOfficeSignInManager(this IOwinContext owinContext) + { + var mgr = owinContext.Get(); + if (mgr == null) + { + throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeSignInManager) + " from the " + typeof(IOwinContext)); + } + return mgr; + } + + /// + /// Gets the back office user manager out of OWIN + /// + /// + /// + /// + /// This is required because to extract the user manager we need to user a custom service since owin only deals in generics and + /// developers could register their own user manager types + /// + public static BackOfficeUserManager GetBackOfficeUserManager(this IOwinContext owinContext) + { + var marker = owinContext.Get(BackOfficeUserManager.OwinMarkerKey); + if (marker == null) throw new NullReferenceException("No " + typeof(IBackOfficeUserManagerMarker) + " has been registered with Owin which means that no Umbraco back office user manager has been registered"); + + var mgr = marker.GetManager(owinContext); + if (mgr == null) + { + throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeUserManager)); + } + return mgr; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index bbae07eb6a..cbcd5ecc05 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Globalization; using System.Linq; using System.Threading; +using System.Xml; using System.Xml.Linq; using Umbraco.Core.Auditing; using Umbraco.Core.Configuration; @@ -536,7 +537,12 @@ namespace Umbraco.Core.Services { query.Where(x => x.ParentId == id); } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); return contents; } @@ -588,6 +594,43 @@ namespace Umbraco.Core.Services { var query = Query.Builder; + //if the id is System Root, then just get all + if (id != Constants.System.Root) + { + query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); + } + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); + + return contents; + } + } + + /// + /// 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) + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder; + //if the id is System Root, then just get all if (id != Constants.System.Root) { @@ -1672,6 +1715,20 @@ namespace Umbraco.Core.Services return true; } + /// + /// This builds the Xml document used for the XML cache + /// + /// + public XmlDocument BuildXmlCache() + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + var result = repository.BuildXmlCache(); + return result; + } + } + /// /// Rebuilds all xml content in the cmsContentXml table for all documents /// diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 34dcdc01a9..1ea43c0f08 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -803,10 +803,13 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateContentTypeRepository(uow)) { + var deletedContentTypes = new List() {contentType}; + deletedContentTypes.AddRange(contentType.Descendants().OfType()); + repository.Delete(contentType); uow.Commit(); - DeletedContentType.RaiseEvent(new DeleteEventArgs(contentType, false), this); + DeletedContentType.RaiseEvent(new DeleteEventArgs(deletedContentTypes.DistinctBy(x => x.Id), false), this); } Audit(AuditType.Delete, string.Format("Delete ContentType performed by user"), userId, contentType.Id); @@ -838,14 +841,18 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateContentTypeRepository(uow)) { + var deletedContentTypes = new List(); + deletedContentTypes.AddRange(asArray); + foreach (var contentType in asArray) { + deletedContentTypes.AddRange(contentType.Descendants().OfType()); repository.Delete(contentType); } uow.Commit(); - DeletedContentType.RaiseEvent(new DeleteEventArgs(asArray, false), this); + DeletedContentType.RaiseEvent(new DeleteEventArgs(deletedContentTypes.DistinctBy(x => x.Id), false), this); } Audit(AuditType.Delete, string.Format("Delete ContentTypes performed by user"), userId, -1); @@ -1238,11 +1245,13 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) { + var deletedMediaTypes = new List() {mediaType}; + deletedMediaTypes.AddRange(mediaType.Descendants().OfType()); repository.Delete(mediaType); uow.Commit(); - DeletedMediaType.RaiseEvent(new DeleteEventArgs(mediaType, false), this); + DeletedMediaType.RaiseEvent(new DeleteEventArgs(deletedMediaTypes.DistinctBy(x => x.Id), false), this); } Audit(AuditType.Delete, string.Format("Delete MediaType performed by user"), userId, mediaType.Id); @@ -1271,13 +1280,17 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) { + var deletedMediaTypes = new List(); + deletedMediaTypes.AddRange(asArray); + foreach (var mediaType in asArray) { + deletedMediaTypes.AddRange(mediaType.Descendants().OfType()); repository.Delete(mediaType); } uow.Commit(); - DeletedMediaType.RaiseEvent(new DeleteEventArgs(asArray, false), this); + DeletedMediaType.RaiseEvent(new DeleteEventArgs(deletedMediaTypes.DistinctBy(x => x.Id), false), this); } Audit(AuditType.Delete, string.Format("Delete MediaTypes performed by user"), userId, -1); diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 035cfd0ab6..2cdcfc76d5 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -504,6 +504,10 @@ namespace Umbraco.Core.Services if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) return; + // if preValues contain the data type, override the data type definition accordingly + if (values != null && values.ContainsKey(Constants.PropertyEditors.PreValueKeys.DataValueType)) + dataTypeDefinition.DatabaseType = PropertyValueEditor.GetDatabaseType(values[Constants.PropertyEditors.PreValueKeys.DataValueType].Value); + var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow)) { @@ -523,7 +527,6 @@ namespace Umbraco.Core.Services Audit(AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); } - /// /// Deletes an /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index df70fa8094..fa4130f8c4 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Xml; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Publishing; namespace Umbraco.Core.Services @@ -93,6 +95,12 @@ namespace Umbraco.Core.Services /// public interface IContentService : IService { + /// + /// This builds the Xml document used for the XML cache + /// + /// + XmlDocument BuildXmlCache(); + /// /// Rebuilds all xml content in the cmsContentXml table for all documents /// @@ -268,6 +276,21 @@ namespace Umbraco.Core.Services IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, bool orderBySystemField, string filter); + /// + /// 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 + /// + /// An Enumerable list of objects + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); + /// /// Gets a collection of an objects versions by its Id /// diff --git a/src/Umbraco.Core/Services/INotificationService.cs b/src/Umbraco.Core/Services/INotificationService.cs index c067b2d615..ccd0821b8a 100644 --- a/src/Umbraco.Core/Services/INotificationService.cs +++ b/src/Umbraco.Core/Services/INotificationService.cs @@ -29,6 +29,20 @@ namespace Umbraco.Core.Services Func createSubject, Func createBody); + /// + /// Sends the notifications for the specified user regarding the specified nodes and action. + /// + /// + /// + /// + /// + /// + /// + /// + void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, HttpContextBase http, + Func createSubject, + Func createBody); + /// /// Gets the notifications for the user /// diff --git a/src/Umbraco.Core/Services/IRedirectUrlService.cs b/src/Umbraco.Core/Services/IRedirectUrlService.cs new file mode 100644 index 0000000000..1249b3b664 --- /dev/null +++ b/src/Umbraco.Core/Services/IRedirectUrlService.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + /// + /// + /// + public interface IRedirectUrlService : IService + { + /// + /// Registers a redirect url. + /// + /// The Umbraco url route. + /// The content unique key. + /// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo. + void Register(string url, Guid contentKey); + + /// + /// Deletes all redirect urls for a given content. + /// + /// The content unique key. + void DeleteContentRedirectUrls(Guid contentKey); + + /// + /// Deletes a redirect url. + /// + /// The redirect url to delete. + void Delete(IRedirectUrl redirectUrl); + + /// + /// Deletes a redirect url. + /// + /// The redirect url identifier. + void Delete(Guid id); + + /// + /// Deletes all redirect urls. + /// + void DeleteAll(); + + /// + /// Gets the most recent redirect urls corresponding to an Umbraco redirect url route. + /// + /// The Umbraco redirect url route. + /// The most recent redirect urls corresponding to the route. + IRedirectUrl GetMostRecentRedirectUrl(string url); + + /// + /// Gets all redirect urls for a content item. + /// + /// The content unique key. + /// All redirect urls for the content item. + IEnumerable GetContentRedirectUrls(Guid contentKey); + + /// + /// Gets all redirect urls. + /// + /// The page index. + /// The page size. + /// The total count of redirect urls. + /// The redirect urls. + IEnumerable GetAllRedirectUrls(long pageIndex, int pageSize, out long total); + + /// + /// Gets all redirect urls below a given content item. + /// + /// The content unique identifier. + /// The page index. + /// The page size. + /// The total count of redirect urls. + /// The redirect urls. + IEnumerable GetAllRedirectUrls(int rootContentId, long pageIndex, int pageSize, out long total); + + /// + /// Searches for all redirect urls that contain a given search term in their URL property. + /// + /// The term to search for. + /// The page index. + /// The page size. + /// The total count of redirect urls. + /// The redirect urls. + IEnumerable SearchRedirectUrls(string searchTerm, long pageIndex, int pageSize, out long total); + } +} diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 924bc8a963..fc0a4821b7 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -622,6 +622,7 @@ namespace Umbraco.Core.Services public IMedia GetMediaByPath(string mediaPath) { var umbracoFileValue = mediaPath; + const string Pattern = ".*[_][0-9]+[x][0-9]+[.].*"; var isResized = Regex.IsMatch(mediaPath, Pattern); @@ -654,6 +655,30 @@ namespace Umbraco.Core.Services propertyDataDto = uow.Database.Fetch(sql).FirstOrDefault(); } + // If no reults far, try getting from a json value stored in the ntext column query + if (propertyDataDto == null) + { + var ntextQuery = new Sql().Select("*") + .From() + .InnerJoin() + .On(left => left.PropertyTypeId, right => right.Id) + .Where(x => x.Alias == "umbracoFile") + .Where("dataNtext LIKE @0", "%" + umbracoFileValue + "%"); + propertyDataDto = uow.Database.Fetch(ntextQuery).FirstOrDefault(); + } + + // If still no results, try getting from a json value stored in the nvarchar column + if (propertyDataDto == null) + { + var nvarcharQuery = new Sql().Select("*") + .From() + .InnerJoin() + .On(left => left.PropertyTypeId, right => right.Id) + .Where(x => x.Alias == "umbracoFile") + .Where("dataNvarchar LIKE @0", "%" + umbracoFileValue + "%"); + propertyDataDto = uow.Database.Fetch(nvarcharQuery).FirstOrDefault(); + } + return propertyDataDto == null ? null : GetById(propertyDataDto.NodeId); } } diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 41e2ff698e..5d51e20008 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -56,37 +56,88 @@ namespace Umbraco.Core.Services Func createBody) { if ((entity is IContent) == false) - { throw new NotSupportedException(); - } - var content = (IContent) entity; - //we'll lazily get these if we need to send notifications - IEnumerable allVersions = null; + + var content = (IContent) entity; + + // lazily get versions - into a list to ensure we can enumerate multiple times + List allVersions = null; int totalUsers; var allUsers = _userService.GetAll(0, int.MaxValue, out totalUsers); - foreach (var u in allUsers) + foreach (var u in allUsers.Where(x => x.IsApproved)) { - if (u.IsApproved == false) continue; - var userNotifications = GetUserNotifications(u, content.Path).ToArray(); + var userNotifications = GetUserNotifications(u, content.Path); var notificationForAction = userNotifications.FirstOrDefault(x => x.Action == action); - if (notificationForAction != null) + if (notificationForAction == null) continue; + + if (allVersions == null) // lazy load + allVersions = _contentService.GetVersions(entity.Id).ToList(); + + try { - //lazy load versions if notifications are required - if (allVersions == null) - { - allVersions = _contentService.GetVersions(entity.Id); - } + SendNotification(operatingUser, u, content, allVersions, + actionName, http, createSubject, createBody); + + _logger.Debug(string.Format("Notification type: {0} sent to {1} ({2})", + action, u.Name, u.Email)); + } + catch (Exception ex) + { + _logger.Error("An error occurred sending notification", ex); + } + } + } + + /// + /// Sends the notifications for the specified user regarding the specified node and action. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// Currently this will only work for Content entities! + /// + public void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, HttpContextBase http, + Func createSubject, + Func createBody) + { + if ((entities is IEnumerable) == false) + throw new NotSupportedException(); + + // ensure we can enumerate multiple times + var entitiesL = entities as List ?? entities.Cast().ToList(); + + // lazily get versions - into lists to ensure we can enumerate multiple times + var allVersionsDictionary = new Dictionary>(); + + int totalUsers; + var allUsers = _userService.GetAll(0, int.MaxValue, out totalUsers); + foreach (var u in allUsers.Where(x => x.IsApproved)) + { + var userNotifications = GetUserNotifications(u).ToArray(); + + foreach (var content in entitiesL) + { + var userNotificationsByPath = FilterUserNotificationsByPath(userNotifications, content.Path); + var notificationForAction = userNotificationsByPath.FirstOrDefault(x => x.Action == action); + if (notificationForAction == null) continue; + + var allVersions = allVersionsDictionary.ContainsKey(content.Id) // lazy load + ? allVersionsDictionary[content.Id] + : allVersionsDictionary[content.Id] = _contentService.GetVersions(content.Id).ToList(); try { - SendNotification( - operatingUser, u, content, - allVersions, + SendNotification(operatingUser, u, content, allVersions, actionName, http, createSubject, createBody); - - _logger.Debug(string.Format("Notification type: {0} sent to {1} ({2})", action, u.Name, u.Email)); + _logger.Debug(string.Format("Notification type: {0} sent to {1} ({2})", + action, u.Name, u.Email)); } catch (Exception ex) { @@ -96,6 +147,7 @@ namespace Umbraco.Core.Services } } + /// /// Gets the notifications for the user /// @@ -119,10 +171,20 @@ namespace Umbraco.Core.Services /// public IEnumerable GetUserNotifications(IUser user, string path) { - var userNotifications = GetUserNotifications(user).ToArray(); - var pathParts = path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); - var result = userNotifications.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList(); - return result; + var userNotifications = GetUserNotifications(user); + return FilterUserNotificationsByPath(userNotifications, path); + } + + /// + /// Filters a userNotifications collection by a path + /// + /// + /// + /// + public IEnumerable FilterUserNotificationsByPath(IEnumerable userNotifications, string path) + { + var pathParts = path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); + return userNotifications.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList(); } /// diff --git a/src/Umbraco.Core/Services/RedirectUrlService.cs b/src/Umbraco.Core/Services/RedirectUrlService.cs new file mode 100644 index 0000000000..0283332401 --- /dev/null +++ b/src/Umbraco.Core/Services/RedirectUrlService.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Services +{ + internal class RedirectUrlService : RepositoryService, IRedirectUrlService + { + public RedirectUrlService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) + { } + + public void Register(string url, Guid contentKey) + { + using (var uow = UowProvider.GetUnitOfWork()) + using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) + { + var redir = repo.Get(url, contentKey); + if (redir != null) + redir.CreateDateUtc = DateTime.UtcNow; + else + redir = new RedirectUrl { Key = Guid.NewGuid(), Url = url, ContentKey = contentKey }; + repo.AddOrUpdate(redir); + uow.Commit(); + } + } + + public void Delete(IRedirectUrl redirectUrl) + { + using (var uow = UowProvider.GetUnitOfWork()) + using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) + { + repo.Delete(redirectUrl); + uow.Commit(); + } + } + + public void Delete(Guid id) + { + using (var uow = UowProvider.GetUnitOfWork()) + using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) + { + repo.Delete(id); + uow.Commit(); + } + } + + public void DeleteContentRedirectUrls(Guid contentKey) + { + using (var uow = UowProvider.GetUnitOfWork()) + using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) + { + repo.DeleteContentUrls(contentKey); + uow.Commit(); + } + } + + public void DeleteAll() + { + using (var uow = UowProvider.GetUnitOfWork()) + using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) + { + repo.DeleteAll(); + uow.Commit(); + } + } + + public IRedirectUrl GetMostRecentRedirectUrl(string url) + { + using (var uow = UowProvider.GetUnitOfWork()) + using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) + { + var rule = repo.GetMostRecentUrl(url); + return rule; + } + } + + public IEnumerable GetContentRedirectUrls(Guid contentKey) + { + using (var uow = UowProvider.GetUnitOfWork()) + using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) + { + var rules = repo.GetContentUrls(contentKey); + return rules; + } + } + + public IEnumerable GetAllRedirectUrls(long pageIndex, int pageSize, out long total) + { + using (var uow = UowProvider.GetUnitOfWork()) + using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) + { + var rules = repo.GetAllUrls(pageIndex, pageSize, out total); + return rules; + } + } + + public IEnumerable GetAllRedirectUrls(int rootContentId, long pageIndex, int pageSize, out long total) + { + using (var uow = UowProvider.GetUnitOfWork()) + using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) + { + var rules = repo.GetAllUrls(rootContentId, pageIndex, pageSize, out total); + return rules; + } + } + public IEnumerable SearchRedirectUrls(string searchTerm, long pageIndex, int pageSize, out long total) + { + using (var uow = UowProvider.GetUnitOfWork()) + using (var repo = RepositoryFactory.CreateRedirectUrlRepository(uow)) + { + var rules = repo.SearchUrls(searchTerm, pageIndex, pageSize, out total); + return rules; + } + } + } +} diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 10601a30fa..a18df0ade1 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -1,5 +1,4 @@ using System; -using log4net; using Umbraco.Core.Logging; using System.IO; using System.Linq; @@ -7,7 +6,6 @@ using Umbraco.Core.IO; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Publishing; -using umbraco.interfaces; using Umbraco.Core.Events; namespace Umbraco.Core.Services @@ -64,6 +62,7 @@ namespace Umbraco.Core.Services private Lazy _memberGroupService; private Lazy _notificationService; private Lazy _externalLoginService; + private Lazy _redirectUrlService; /// /// public ctor - will generally just be used for unit testing all items are optional and if not specified, the defaults will be used @@ -93,6 +92,7 @@ namespace Umbraco.Core.Services /// /// /// + /// public ServiceContext( IContentService contentService = null, IMediaService mediaService = null, @@ -118,7 +118,8 @@ namespace Umbraco.Core.Services IMacroService macroService = null, IPublicAccessService publicAccessService = null, IExternalLoginService externalLoginService = null, - IMigrationEntryService migrationEntryService = null) + IMigrationEntryService migrationEntryService = null, + IRedirectUrlService redirectUrlService = null) { if (migrationEntryService != null) _migrationEntryService = new Lazy(() => migrationEntryService); if (externalLoginService != null) _externalLoginService = new Lazy(() => externalLoginService); @@ -145,6 +146,7 @@ namespace Umbraco.Core.Services 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); } /// @@ -310,6 +312,8 @@ namespace Umbraco.Core.Services if (_memberGroupService == null) _memberGroupService = new Lazy(() => new MemberGroupService(provider, repositoryFactory, logger, eventMessagesFactory)); + if (_redirectUrlService == null) + _redirectUrlService = new Lazy(() => new RedirectUrlService(provider, repositoryFactory, logger, eventMessagesFactory)); } /// @@ -516,5 +520,13 @@ namespace Umbraco.Core.Services { get { return _externalLoginService.Value; } } + + /// + /// Gets the RedirectUrlService. + /// + public IRedirectUrlService RedirectUrlService + { + get { return _redirectUrlService.Value; } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index b92df5f1cf..036b5b979f 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -666,31 +666,12 @@ namespace Umbraco.Core return compare.Contains(compareTo, StringComparer.InvariantCultureIgnoreCase); } - /// - /// Determines if the string is a Guid - /// - /// - /// - /// + [Obsolete("Use Guid.TryParse instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static bool IsGuid(this string str, bool withHyphens) { - var isGuid = false; - - if (!String.IsNullOrEmpty(str)) - { - Regex guidRegEx; - if (withHyphens) - { - guidRegEx = new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$"); - } - else - { - guidRegEx = new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}([0-9a-fA-F]){4}([0-9a-fA-F]){4}([0-9a-fA-F]){4}([0-9a-fA-F]){12}\}{0,1})$"); - } - isGuid = guidRegEx.IsMatch(str); - } - - return isGuid; + Guid g; + return Guid.TryParse(str, out g); } /// @@ -712,7 +693,7 @@ namespace Umbraco.Core /// public static object ParseInto(this string val, Type type) { - if (!String.IsNullOrEmpty(val)) + if (string.IsNullOrEmpty(val) == false) { TypeConverter tc = TypeDescriptor.GetConverter(type); return tc.ConvertFrom(val); @@ -750,6 +731,36 @@ namespace Umbraco.Core return stringBuilder.ToString(); } + /// + /// Converts the string to SHA1 + /// + /// referrs to itself + /// the md5 hashed string + public static string ToSHA1(this string stringToConvert) + { + //create an instance of the SHA1CryptoServiceProvider + var md5Provider = new SHA1CryptoServiceProvider(); + + //convert our string into byte array + var byteArray = Encoding.UTF8.GetBytes(stringToConvert); + + //get the hashed values created by our SHA1CryptoServiceProvider + var hashedByteArray = md5Provider.ComputeHash(byteArray); + + //create a StringBuilder object + var stringBuilder = new StringBuilder(); + + //loop to each each byte + foreach (var b in hashedByteArray) + { + //append it to our StringBuilder + stringBuilder.Append(b.ToString("x2").ToLower()); + } + + //return the hashed value + return stringBuilder.ToString(); + } + /// /// Decodes a string that was encoded with UrlTokenEncode diff --git a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs index 260e8e5159..58dddb5712 100644 --- a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs +++ b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.Strings.Css { internal class StylesheetHelper { - private const string RuleRegexFormat = @"/\*\*\s*umb_name:\s*(?{0}?)\s*\*/\s*(?[^\s,{{]*?)\s*{{\s*(?.*?)\s*}}"; + private const string RuleRegexFormat = @"/\*\*\s*umb_name:\s*(?{0}?)\s*\*/\s*(?[^,{{]*?)\s*{{\s*(?.*?)\s*}}"; public static IEnumerable ParseRules(string input) { diff --git a/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs b/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs index dc3f4243e7..c3ef596ad0 100644 --- a/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs +++ b/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs @@ -26,5 +26,9 @@ namespace Umbraco.Core.Strings /// per content, in 1-to-1 multilingual configurations. Then there would be one /// url per culture. string GetUrlSegment(IContentBase content, CultureInfo culture); + + //TODO: For the 301 tracking, we need to add another extended interface to this so that + // the RedirectTrackingEventHandler can ask the IUrlSegmentProvider if the URL is changing. + // Currently the way it works is very hacky, see notes in: RedirectTrackingEventHandler.ContentService_Publishing } } diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index d8d828b23e..4e46c0ab5c 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -35,6 +35,7 @@ namespace Umbraco.Core.Sync private readonly ILogger _logger; private int _lastId = -1; private DateTime _lastSync; + private DateTime _lastPruned; private bool _initialized; private bool _syncing; private bool _released; @@ -50,7 +51,7 @@ namespace Umbraco.Core.Sync _appContext = appContext; _options = options; - _lastSync = DateTime.UtcNow; + _lastPruned = _lastSync = DateTime.UtcNow; _syncIdle = new ManualResetEvent(true); _profilingLogger = appContext.ProfilingLogger; _logger = appContext.ProfilingLogger.Logger; @@ -213,6 +214,12 @@ namespace Umbraco.Core.Sync using (_profilingLogger.DebugDuration("Syncing from database...")) { ProcessDatabaseInstructions(); + + if ((DateTime.UtcNow - _lastPruned).TotalSeconds <= _options.PruneThrottleSeconds) + return; + + _lastPruned = _lastSync; + switch (_appContext.GetCurrentServerRole()) { case ServerRole.Single: @@ -235,6 +242,9 @@ namespace Umbraco.Core.Sync /// /// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded. /// + /// + /// Returns the number of processed instructions + /// private void ProcessDatabaseInstructions() { // NOTE @@ -313,50 +323,53 @@ namespace Umbraco.Core.Sync private void PruneOldInstructions() { var pruneDate = DateTime.UtcNow.AddDays(-_options.DaysToRetainInstructions); - var sqlSyntax = _appContext.DatabaseContext.SqlSyntax; - //NOTE: this query could work on SQL server and MySQL: - /* - SELECT id - FROM umbracoCacheInstruction - WHERE utcStamp < getdate() - AND id <> (SELECT MAX(id) FROM umbracoCacheInstruction) - */ - // However, this will not work on SQLCE and in fact it will be slower than the query we are - // using if the SQL server doesn't perform it's own query optimizations (i.e. since the above - // query could actually execute a sub query for every row found). So we've had to go with an - // inner join which is faster and works on SQLCE but it's uglier to read. + // using 2 queries is faster than convoluted joins - var deleteQuery = new Sql().Select("cacheIns.id") - .From("umbracoCacheInstruction cacheIns") - .InnerJoin("(SELECT MAX(id) id FROM umbracoCacheInstruction) tMax") - .On("cacheIns.id <> tMax.id") - .Where("cacheIns.utcStamp < @pruneDate", new {pruneDate = pruneDate}); + var maxId = _appContext.DatabaseContext.Database.ExecuteScalar("SELECT MAX(id) FROM umbracoCacheInstruction;"); - var deleteSql = sqlSyntax.GetDeleteSubquery( - "umbracoCacheInstruction", - "id", - deleteQuery); + var delete = new Sql().Append(@"DELETE FROM umbracoCacheInstruction WHERE utcStamp < @pruneDate AND id < @maxId", + new { pruneDate, maxId }); - _appContext.DatabaseContext.Database.Execute(deleteSql); + _appContext.DatabaseContext.Database.Execute(delete); } /// /// Ensure that the last instruction that was processed is still in the database. /// - /// If the last instruction is not in the database anymore, then the messenger + /// + /// If the last instruction is not in the database anymore, then the messenger /// should not try to process any instructions, because some instructions might be lost, - /// and it should instead cold-boot. + /// and it should instead cold-boot. + /// However, if the last synced instruction id is '0' and there are '0' records, then this indicates + /// that it's a fresh site and no user actions have taken place, in this circumstance we do not want to cold + /// boot. See: http://issues.umbraco.org/issue/U4-8627 + /// private void EnsureInstructions() { - var sql = new Sql().Select("*") + if (_lastId == 0) + { + var sql = new Sql().Select("COUNT(*)") + .From(_appContext.DatabaseContext.SqlSyntax); + + var count = _appContext.DatabaseContext.Database.ExecuteScalar(sql); + + //if there are instructions but we haven't synced, then a cold boot is necessary + if (count > 0) + _lastId = -1; + } + else + { + var sql = new Sql().Select("*") .From(_appContext.DatabaseContext.SqlSyntax) .Where(dto => dto.Id == _lastId); - var dtos = _appContext.DatabaseContext.Database.Fetch(sql); + var dtos = _appContext.DatabaseContext.Database.Fetch(sql); - if (dtos.Count == 0) - _lastId = -1; + //if the last synced instruction is not found in the db, then a cold boot is necessary + if (dtos.Count == 0) + _lastId = -1; + } } /// @@ -399,7 +412,7 @@ namespace Umbraco.Core.Sync /// Practically, all we really need is the guid, the other infos are here for information /// and debugging purposes. /// - protected readonly static string LocalIdentity = NetworkHelper.MachineName // eg DOMAIN\SERVER + protected static readonly string LocalIdentity = NetworkHelper.MachineName // eg DOMAIN\SERVER + "/" + HttpRuntime.AppDomainAppId // eg /LM/S3SVC/11/ROOT + " [P" + Process.GetCurrentProcess().Id // eg 1234 + "/D" + AppDomain.CurrentDomain.Id // eg 22 diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs b/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs index 7559c37813..c38a0c2568 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs @@ -16,6 +16,7 @@ namespace Umbraco.Core.Sync DaysToRetainInstructions = 2; // 2 days ThrottleSeconds = 5; // 5 second MaxProcessingInstructionCount = 1000; + PruneThrottleSeconds = 60; // 1 minute } /// @@ -41,5 +42,10 @@ namespace Umbraco.Core.Sync /// The number of seconds to wait between each sync operations. /// public int ThrottleSeconds { get; set; } + + /// + /// The number of seconds to wait between each prune operations. + /// + public int PruneThrottleSeconds { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 366eb38066..41366110e7 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -52,6 +52,10 @@ ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + + ..\packages\ImageProcessor.2.4.4.0\lib\net45\ImageProcessor.dll + True + False ..\packages\log4net-mediumtrust.2.0.0\lib\log4net.dll @@ -110,6 +114,7 @@ False ..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll + @@ -305,6 +310,7 @@ + @@ -352,8 +358,11 @@ + + + @@ -375,6 +384,7 @@ + @@ -398,6 +408,7 @@ + @@ -418,6 +429,12 @@ + + + + + + @@ -470,30 +487,37 @@ + + + + + + + @@ -507,9 +531,12 @@ + + + @@ -1071,7 +1098,6 @@ - diff --git a/src/Umbraco.Core/XmlHelper.cs b/src/Umbraco.Core/XmlHelper.cs index 1978c329ab..66dcae7358 100644 --- a/src/Umbraco.Core/XmlHelper.cs +++ b/src/Umbraco.Core/XmlHelper.cs @@ -17,6 +17,35 @@ namespace Umbraco.Core /// public class XmlHelper { + /// + /// Creates or sets an attribute on the XmlNode if an Attributes collection is available + /// + /// + /// + /// + /// + public static void SetAttribute(XmlDocument xml, XmlNode n, string name, string value) + { + if (xml == null) throw new ArgumentNullException("xml"); + if (n == null) throw new ArgumentNullException("n"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + + if (n.Attributes == null) + { + return; + } + if (n.Attributes[name] == null) + { + var a = xml.CreateAttribute(name); + a.Value = value; + n.Attributes.Append(a); + } + else + { + n.Attributes[name].Value = value; + } + } + /// /// Gets a value indicating whether a specified string contains only xml whitespace characters. /// @@ -210,6 +239,9 @@ namespace Umbraco.Core var childNodesAndOrder = parentNode.SelectNodes(childNodesXPath).Cast() .Select(x => Tuple.Create(x, orderBy(x))).ToArray(); + // only one node = node is in the right place already, obviously + if (childNodesAndOrder.Length == 1) return false; + // find the first node with a sortOrder > node.sortOrder var i = 0; while (i < childNodesAndOrder.Length && childNodesAndOrder[i].Item2 <= nodeSortOrder) @@ -220,17 +252,18 @@ namespace Umbraco.Core { // and node is just before, we're done already // else we need to move it right before the node that was found - if (i > 0 && childNodesAndOrder[i - 1].Item1 != node) + if (i == 0 || childNodesAndOrder[i - 1].Item1 != node) { parentNode.InsertBefore(node, childNodesAndOrder[i].Item1); return true; } } - else + else // i == childNodesAndOrder.Length && childNodesAndOrder.Length > 1 { // and node is the last one, we're done already // else we need to append it as the last one - if (i > 0 && childNodesAndOrder[i - 1].Item1 != node) + // (and i > 1, see above) + if (childNodesAndOrder[i - 1].Item1 != node) { parentNode.AppendChild(node); return true; @@ -334,6 +367,9 @@ namespace Umbraco.Core /// a XmlAttribute public static XmlAttribute AddAttribute(XmlDocument xd, string name, string value) { + if (xd == null) throw new ArgumentNullException("xd"); + if (string.IsNullOrEmpty(name)) throw new ArgumentException("Value cannot be null or empty.", "name"); + var temp = xd.CreateAttribute(name); temp.Value = value; return temp; @@ -348,11 +384,37 @@ namespace Umbraco.Core /// a XmlNode public static XmlNode AddTextNode(XmlDocument xd, string name, string value) { + if (xd == null) throw new ArgumentNullException("xd"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + var temp = xd.CreateNode(XmlNodeType.Element, name, ""); temp.AppendChild(xd.CreateTextNode(value)); return temp; } + /// + /// Sets or Creates a text XmlNode with the specified name and value + /// + /// The xmldocument. + /// The node to set or create the child text node on + /// The node name. + /// The node value. + /// a XmlNode + public static XmlNode SetTextNode(XmlDocument xd, XmlNode parent, string name, string value) + { + if (xd == null) throw new ArgumentNullException("xd"); + if (parent == null) throw new ArgumentNullException("parent"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + + var child = parent.SelectSingleNode(name); + if (child != null) + { + child.InnerText = value; + return child; + } + return AddTextNode(xd, name, value); + } + /// /// Creates a cdata XmlNode with the specified name and value /// @@ -362,11 +424,37 @@ namespace Umbraco.Core /// A XmlNode public static XmlNode AddCDataNode(XmlDocument xd, string name, string value) { + if (xd == null) throw new ArgumentNullException("xd"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + var temp = xd.CreateNode(XmlNodeType.Element, name, ""); temp.AppendChild(xd.CreateCDataSection(value)); return temp; } + /// + /// Sets or Creates a cdata XmlNode with the specified name and value + /// + /// The xmldocument. + /// The node to set or create the child text node on + /// The node name. + /// The node value. + /// a XmlNode + public static XmlNode SetCDataNode(XmlDocument xd, XmlNode parent, string name, string value) + { + if (xd == null) throw new ArgumentNullException("xd"); + if (parent == null) throw new ArgumentNullException("parent"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + + var child = parent.SelectSingleNode(name); + if (child != null) + { + child.InnerXml = ""; ; + return child; + } + return AddCDataNode(xd, name, value); + } + /// /// Gets the value of a XmlNode /// diff --git a/src/Umbraco.Core/packages.config b/src/Umbraco.Core/packages.config index b69be5b5cc..155fead690 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -2,6 +2,7 @@ + diff --git a/src/Umbraco.Tests/AttemptTests.cs b/src/Umbraco.Tests/AttemptTests.cs index 5fd4da7f8e..a912fdf2a7 100644 --- a/src/Umbraco.Tests/AttemptTests.cs +++ b/src/Umbraco.Tests/AttemptTests.cs @@ -37,5 +37,19 @@ namespace Umbraco.Tests i => Assert.AreEqual("finished", i), exception => Assert.Fail("Should have been successful.")); } + + [Test] + public void AttemptIf() + { + // just making sure that it is ok to use TryParse as a condition + + int value; + var attempt = Attempt.If(int.TryParse("1234", out value), value); + Assert.IsTrue(attempt.Success); + Assert.AreEqual(1234, attempt.Result); + + attempt = Attempt.If(int.TryParse("12xxx34", out value), value); + Assert.IsFalse(attempt.Success); + } } } diff --git a/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs b/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs index 39e5dd2cb1..bea89dea0f 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Reflection; using System.Web; using NUnit.Framework; @@ -88,7 +89,7 @@ namespace Umbraco.Tests.Cache private static string GetValue(int i) { - Console.WriteLine("get" + i); + Debug.Print("get" + i); if (i < 3) throw new Exception("fail"); return "succ" + i; @@ -101,20 +102,18 @@ namespace Umbraco.Tests.Cache CloneId = Guid.NewGuid(); } - private static readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + } private string _name; public string Name { get { return _name; } - set - { - SetPropertyValueAndDetectChanges(o => - { - _name = value; - return _name; - }, _name, WriterSelector); - } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.WriterSelector); } } public Guid CloneId { get; set; } diff --git a/src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs b/src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs index 87e6d4366e..d1196b7d66 100644 --- a/src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Web; using NUnit.Framework; using Umbraco.Core.Cache; @@ -49,7 +50,7 @@ namespace Umbraco.Tests.Cache private static string GetValue(int i) { - Console.WriteLine("get" + i); + Debug.Print("get" + i); if (i < 3) throw new Exception("fail"); return "succ" + i; diff --git a/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs b/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs index ee9080b094..c9550f89c1 100644 --- a/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs +++ b/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.IO; using System.Linq; using NUnit.Framework; @@ -60,7 +61,7 @@ namespace Umbraco.Tests.CodeFirst var result = SerializationService.ToStream(contentType.Value); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } [Test] @@ -91,7 +92,7 @@ namespace Umbraco.Tests.CodeFirst var result = SerializationService.ToStream(contentType.Value); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } [Test] diff --git a/src/Umbraco.Tests/CodeFirst/Definitions/ContentTypeDefinitionFactory.cs b/src/Umbraco.Tests/CodeFirst/Definitions/ContentTypeDefinitionFactory.cs index 08dbd89fca..5df7849430 100644 --- a/src/Umbraco.Tests/CodeFirst/Definitions/ContentTypeDefinitionFactory.cs +++ b/src/Umbraco.Tests/CodeFirst/Definitions/ContentTypeDefinitionFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using Umbraco.Core; @@ -210,11 +211,11 @@ namespace Umbraco.Tests.CodeFirst.Definitions { var field = fields[sortOrder[i]]; list.Add(field.ContentType.Value); - Console.WriteLine(field.Alias); + Debug.Print(field.Alias); if (field.DependsOn != null) foreach (var item in field.DependsOn) { - Console.WriteLine(" -{0}", item); + Debug.Print(" -{0}", item); } } list.Reverse(); diff --git a/src/Umbraco.Tests/CodeFirst/StronglyTypedMapperTest.cs b/src/Umbraco.Tests/CodeFirst/StronglyTypedMapperTest.cs index 1e2302087b..5fe2efaa2c 100644 --- a/src/Umbraco.Tests/CodeFirst/StronglyTypedMapperTest.cs +++ b/src/Umbraco.Tests/CodeFirst/StronglyTypedMapperTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using NUnit.Framework; @@ -84,7 +85,7 @@ namespace Umbraco.Tests.CodeFirst }; var type = new AutoPublishedContentType(0, "anything", propertyTypes); PublishedContentType.GetPublishedContentTypeCallback = (alias) => type; - Console.WriteLine("INIT STRONG {0}", + Debug.Print("INIT STRONG {0}", PublishedContentType.Get(PublishedItemType.Content, "anything") .PropertyTypes.Count()); } diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs index 47b5e15b69..1e568c608e 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/WebRoutingElementDefaultTests.cs @@ -28,5 +28,11 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings { Assert.IsTrue(SettingsSection.WebRouting.DisableFindContentByIdPath == false); } + + [Test] + public void DisableRedirectUrlTracking() + { + Assert.IsTrue(SettingsSection.WebRouting.DisableRedirectUrlTracking == false); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs b/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs index 41414cb81a..bf5100815f 100644 --- a/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs +++ b/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs @@ -726,8 +726,8 @@ namespace Umbraco.Tests.CoreXml // but was NOT working (changing the order of nodes) with macro nav, debug // was due to an issue with macro nav IsSamePosition, fixed - //Console.WriteLine("--------"); - //Console.WriteLine(writer.ToString()); + //Debug.Print("--------"); + //Debug.Print(writer.ToString()); Assert.AreEqual(expected.Lf(), writer.ToString().Lf()); } diff --git a/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs b/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs index 33965c40c5..550310d164 100644 --- a/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs +++ b/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; @@ -305,7 +306,7 @@ namespace Umbraco.Tests.DynamicsAndReflection var parameterType = parameters[i].ParameterType; var argumentType = arguments[i].GetType(); - Console.WriteLine("{0} / {1}", parameterType, argumentType); + Debug.Print("{0} / {1}", parameterType, argumentType); if (parameterType == argumentType) continue; // match if (parameterType.IsGenericParameter) // eg T @@ -334,7 +335,7 @@ namespace Umbraco.Tests.DynamicsAndReflection // then what ?! // should _variance_ be of some importance? - Console.WriteLine("generic {0}", argumentType.IsGenericType); + Debug.Print("generic {0}", argumentType.IsGenericType); } else { diff --git a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs index 035a76d052..672b9022e5 100644 --- a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Web; namespace Umbraco.Tests.FrontEnd @@ -48,5 +45,38 @@ namespace Umbraco.Tests.FrontEnd Assert.AreEqual("Hello world, this is some text with…", result); } + [Test] + public void Create_Encrypted_RouteString_From_Anonymous_Object() + { + var additionalRouteValues = new + { + key1 = "value1", + key2 = "value2", + Key3 = "Value3", + keY4 = "valuE4" + }; + var encryptedRouteString = UmbracoHelper.CreateEncryptedRouteString("FormController", "FormAction", "", additionalRouteValues); + var result = encryptedRouteString.DecryptWithMachineKey(); + var expectedResult = "c=FormController&a=FormAction&ar=&key1=value1&key2=value2&Key3=Value3&keY4=valuE4"; + + Assert.AreEqual(expectedResult, result); + } + + [Test] + public void Create_Encrypted_RouteString_From_Dictionary() + { + var additionalRouteValues = new Dictionary() + { + {"key1", "value1"}, + {"key2", "value2"}, + {"Key3", "Value3"}, + {"keY4", "valuE4"} + }; + var encryptedRouteString = UmbracoHelper.CreateEncryptedRouteString("FormController", "FormAction", "", additionalRouteValues); + var result = encryptedRouteString.DecryptWithMachineKey(); + var expectedResult = "c=FormController&a=FormAction&ar=&key1=value1&key2=value2&Key3=Value3&keY4=valuE4"; + + Assert.AreEqual(expectedResult, result); + } } } diff --git a/src/Umbraco.Tests/LibraryTests.cs b/src/Umbraco.Tests/LibraryTests.cs index dca6215d90..086395f456 100644 --- a/src/Umbraco.Tests/LibraryTests.cs +++ b/src/Umbraco.Tests/LibraryTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -44,7 +45,7 @@ namespace Umbraco.Tests }; var type = new AutoPublishedContentType(0, "anything", propertyTypes); PublishedContentType.GetPublishedContentTypeCallback = (alias) => type; - Console.WriteLine("INIT LIB {0}", + Debug.Print("INIT LIB {0}", PublishedContentType.Get(PublishedItemType.Content, "anything") .PropertyTypes.Count()); diff --git a/src/Umbraco.Tests/Logging/AsyncRollingFileAppenderTest.cs b/src/Umbraco.Tests/Logging/AsyncRollingFileAppenderTest.cs index ddd1c19095..c5e37f424a 100644 --- a/src/Umbraco.Tests/Logging/AsyncRollingFileAppenderTest.cs +++ b/src/Umbraco.Tests/Logging/AsyncRollingFileAppenderTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -160,7 +161,7 @@ namespace Umbraco.Tests.Logging // Assert var logsPerSecond = logCount / testDuration.TotalSeconds; - Console.WriteLine("{0} messages logged in {1}s => {2}/s", logCount, testDuration.TotalSeconds, logsPerSecond); + Debug.Print("{0} messages logged in {1}s => {2}/s", logCount, testDuration.TotalSeconds, logsPerSecond); Assert.That(logsPerSecond, Is.GreaterThan(1000), "Must log at least 1000 messages per second"); } } diff --git a/src/Umbraco.Tests/Logging/DebugAppender.cs b/src/Umbraco.Tests/Logging/DebugAppender.cs index a483103992..db78a96965 100644 --- a/src/Umbraco.Tests/Logging/DebugAppender.cs +++ b/src/Umbraco.Tests/Logging/DebugAppender.cs @@ -5,17 +5,16 @@ using log4net.Core; namespace Umbraco.Tests.Logging { - internal class DebugAppender : MemoryAppender + /// + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 + /// + public class DebugAppender : MemoryAppender { public TimeSpan AppendDelay { get; set; } public int LoggedEventCount { get { return m_eventsList.Count; } } - public bool Cancel { get; set; } - protected override void Append(LoggingEvent loggingEvent) { - if (Cancel) return; - if (AppendDelay > TimeSpan.Zero) { Thread.Sleep(AppendDelay); diff --git a/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs b/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs index d347395b1f..88471ab650 100644 --- a/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs +++ b/src/Umbraco.Tests/Logging/ParallelForwarderTest.cs @@ -14,6 +14,9 @@ using Umbraco.Core.Logging; namespace Umbraco.Tests.Logging { + /// + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 + /// [TestFixture] public class ParallelForwarderTest : IDisposable { @@ -84,7 +87,7 @@ namespace Umbraco.Tests.Logging badAppender .Setup(ba => ba.DoAppend(It.IsAny())) .Throws(new Exception("Bad Appender")); - //.Verifiable(); + //.Verifiable(); // Act log.Info("InitialMessage"); @@ -103,7 +106,7 @@ namespace Umbraco.Tests.Logging const int testSize = 1000; // Arrange - debugAppender.AppendDelay = TimeSpan.FromSeconds(10); + debugAppender.AppendDelay = TimeSpan.FromSeconds(30); var watch = new Stopwatch(); // Act @@ -117,9 +120,7 @@ namespace Umbraco.Tests.Logging // Assert Assert.That(debugAppender.LoggedEventCount, Is.EqualTo(0)); Assert.That(watch.ElapsedMilliseconds, Is.LessThan(testSize)); - Console.WriteLine("Logged {0} errors in {1}ms", testSize, watch.ElapsedMilliseconds); - - debugAppender.Cancel = true; + Debug.Print("Logged {0} errors in {1}ms", testSize, watch.ElapsedMilliseconds); } [Test] @@ -170,11 +171,10 @@ namespace Umbraco.Tests.Logging //On some systems, we may not be able to flush all events prior to close, but it is reasonable to assume in this test case //that some events should be logged after close. Assert.That(numberLoggedAfterClose, Is.GreaterThan(numberLoggedBeforeClose), "Some number of LoggingEvents should be logged after close."); - Console.WriteLine("Flushed {0} events during shutdown", numberLoggedAfterClose - numberLoggedBeforeClose); + Debug.Print("Flushed {0} events during shutdown", numberLoggedAfterClose - numberLoggedBeforeClose); } - [NUnit.Framework.Ignore("Keeps failing very randomly")] - [Test] + [Test, Explicit("Long-running")] public void WillShutdownIfBufferCannotBeFlushedFastEnough() { const int testSize = 250; @@ -206,7 +206,7 @@ namespace Umbraco.Tests.Logging var events = debugAppender.GetEvents(); var evnt = events[events.Length - 1]; Assert.That(evnt.MessageObject, Is.EqualTo("The buffer was not able to be flushed before timeout occurred.")); - Console.WriteLine("Flushed {0} events during shutdown which lasted {1}ms", numberLoggedAfterClose - numberLoggedBeforeClose, watch.ElapsedMilliseconds); + Debug.Print("Flushed {0} events during shutdown which lasted {1}ms", numberLoggedAfterClose - numberLoggedBeforeClose, watch.ElapsedMilliseconds); } [Test] diff --git a/src/Umbraco.Tests/Logging/RingBufferTest.cs b/src/Umbraco.Tests/Logging/RingBufferTest.cs index 8e80faf8ad..69c58b8631 100644 --- a/src/Umbraco.Tests/Logging/RingBufferTest.cs +++ b/src/Umbraco.Tests/Logging/RingBufferTest.cs @@ -9,6 +9,9 @@ using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Logging { + /// + /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 + /// [TestFixture] public class RingBufferTest { diff --git a/src/Umbraco.Tests/Migrations/AlterMigrationTests.cs b/src/Umbraco.Tests/Migrations/AlterMigrationTests.cs index 94e8802ca4..4e38f427e5 100644 --- a/src/Umbraco.Tests/Migrations/AlterMigrationTests.cs +++ b/src/Umbraco.Tests/Migrations/AlterMigrationTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using Moq; using NUnit.Framework; @@ -45,11 +46,11 @@ namespace Umbraco.Tests.Migrations Assert.That(context.Expressions.Any(), Is.True); //Console output - Console.WriteLine("Number of expressions in context: {0}", context.Expressions.Count); - Console.WriteLine(""); + Debug.Print("Number of expressions in context: {0}", context.Expressions.Count); + Debug.Print(""); foreach (var expression in context.Expressions) { - Console.WriteLine(expression.ToString()); + Debug.Print(expression.ToString()); } } } diff --git a/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs b/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs index eb3c36ce42..6d49205adb 100644 --- a/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs +++ b/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Moq; using NUnit.Framework; @@ -87,7 +88,7 @@ namespace Umbraco.Tests.Migrations //Console output foreach (var expression in context.Expressions) { - Console.WriteLine(expression.ToString()); + Debug.Print(expression.ToString()); } } diff --git a/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs b/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs new file mode 100644 index 0000000000..8b51037d14 --- /dev/null +++ b/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs @@ -0,0 +1,147 @@ +using System; +using System.Diagnostics; +using System.Linq; +using Moq; +using NUnit.Framework; +using Semver; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Migrations; +using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven; +using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero; +using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; + +namespace Umbraco.Tests.Migrations +{ + [TestFixture] + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + public class MigrationIssuesTests : BaseDatabaseFactoryTest + { + [Test] + public void Issue8370Test() + { + // fixme maybe we need to create some content? + // yes otherwise cannot get it to fail! + + var n = new NodeDto + { + Text = "text", + CreateDate = DateTime.Now, + Path = "-1", + ParentId = -1, + UniqueId = Guid.NewGuid() + }; + DatabaseContext.Database.Insert(n); + var ct = new ContentTypeDto + { + Alias = "alias", + NodeId = n.NodeId, + Thumbnail = "thumb" + }; + DatabaseContext.Database.Insert(ct); + n = new NodeDto + { + Text = "text", + CreateDate = DateTime.Now, + Path = "-1", + ParentId = -1, + UniqueId = Guid.NewGuid() + }; + DatabaseContext.Database.Insert(n); + var dt = new DataTypeDto + { + PropertyEditorAlias = Constants.PropertyEditors.RelatedLinksAlias, + DbType = "x", + DataTypeId = n.NodeId + }; + DatabaseContext.Database.Insert(dt); + var pt = new PropertyTypeDto + { + Alias = "alias", + ContentTypeId = ct.NodeId, + DataTypeId = dt.DataTypeId + }; + DatabaseContext.Database.Insert(pt); + n = new NodeDto + { + Text = "text", + CreateDate = DateTime.Now, + Path = "-1", + ParentId = -1, + UniqueId = Guid.NewGuid() + }; + DatabaseContext.Database.Insert(n); + var data = new PropertyDataDto + { + NodeId = n.NodeId, + PropertyTypeId = pt.Id, + Text = "text", + VersionId = Guid.NewGuid() + }; + DatabaseContext.Database.Insert(data); + data = new PropertyDataDto + { + NodeId = n.NodeId, + PropertyTypeId = pt.Id, + Text = "", + VersionId = Guid.NewGuid() + }; + DatabaseContext.Database.Insert(data); + + var migration = new UpdateRelatedLinksData(SqlSyntax, Logger); + migration.UpdateRelatedLinksDataDo(DatabaseContext.Database); + + data = DatabaseContext.Database.Fetch("SELECT * FROM cmsPropertyData WHERE id=" + data.Id).FirstOrDefault(); + Assert.IsNotNull(data); + Debug.Print(data.Text); + Assert.AreEqual("[{\"title\":\"\",\"caption\":\"\",\"link\":\"\",\"newWindow\":false,\"type\":\"external\",\"internal\":null,\"edit\":false,\"isInternal\":false}]", + data.Text); + } + + [Test] + public void Issue8361Test() + { + var logger = new DebugDiagnosticsLogger(); + + //Setup the MigrationRunner + var migrationRunner = new MigrationRunner( + Mock.Of(), + logger, + new SemVersion(7, 4, 0), + new SemVersion(7, 5, 0), + GlobalSettings.UmbracoMigrationName, + + //pass in explicit migrations + new DeleteRedirectUrlTable(SqlSyntax, logger), + new AddRedirectUrlTable(SqlSyntax, logger) + ); + + var db = new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", Constants.DatabaseProviders.SqlCe, Logger); + + var upgraded = migrationRunner.Execute(db, DatabaseProviders.SqlServerCE, true); + Assert.IsTrue(upgraded); + } + + [Migration("7.5.0", 99, GlobalSettings.UmbracoMigrationName)] + public class DeleteRedirectUrlTable : MigrationBase + { + public DeleteRedirectUrlTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + Delete.Table("umbracoRedirectUrl"); + } + + public override void Down() + { } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs b/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs index 8d8682ecc5..51822c8098 100644 --- a/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs +++ b/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data.SqlServerCe; +using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using Moq; @@ -89,7 +90,7 @@ namespace Umbraco.Tests.Migrations foreach (var expression in context.Expressions) { - Console.WriteLine(expression.ToString()); + Debug.Print(expression.ToString()); } Assert.That(migrations.Count(), Is.EqualTo(12)); @@ -99,7 +100,7 @@ namespace Umbraco.Tests.Migrations public UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Mock.Of()); + return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", Constants.DatabaseProviders.SqlCe, Mock.Of()); } diff --git a/src/Umbraco.Tests/Migrations/Upgrades/MySqlUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/MySqlUpgradeTest.cs index bdcb55a4a7..9026c3769f 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/MySqlUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/MySqlUpgradeTest.cs @@ -1,5 +1,6 @@ using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; @@ -26,7 +27,7 @@ namespace Umbraco.Tests.Migrations.Upgrades public override UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase("Server = 169.254.120.3; Database = upgradetest; Uid = umbraco; Pwd = umbraco", "MySql.Data.MySqlClient", Mock.Of()); + return new UmbracoDatabase("Server = 169.254.120.3; Database = upgradetest; Uid = umbraco; Pwd = umbraco", Constants.DatabaseProviders.MySql, Mock.Of()); } public override DatabaseProviders GetDatabaseProvider() diff --git a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs index 39ff745e0c..2ea7a40e57 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs @@ -3,6 +3,7 @@ using Moq; using NUnit.Framework; using Semver; using SQLCE4Umbraco; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Migrations; @@ -64,7 +65,7 @@ namespace Umbraco.Tests.Migrations.Upgrades public override UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Mock.Of()); + return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", Constants.DatabaseProviders.SqlCe, Mock.Of()); } public override DatabaseProviders GetDatabaseProvider() diff --git a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs index 0f8e0543ad..f7080b7bc0 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs @@ -5,6 +5,7 @@ using System.IO; using Moq; using NUnit.Framework; using SQLCE4Umbraco; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; @@ -42,8 +43,10 @@ namespace Umbraco.Tests.Migrations.Upgrades //Create the Sql CE database //Get the connectionstring settings from config var settings = ConfigurationManager.ConnectionStrings[Core.Configuration.GlobalSettings.UmbracoConnectionName]; - var engine = new SqlCeEngine(settings.ConnectionString); - engine.CreateDatabase(); + using (var engine = new SqlCeEngine(settings.ConnectionString)) + { + engine.CreateDatabase(); + } } else { @@ -67,7 +70,7 @@ namespace Umbraco.Tests.Migrations.Upgrades public override UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Mock.Of()); + return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", Constants.DatabaseProviders.SqlCe, Mock.Of()); } public override DatabaseProviders GetDatabaseProvider() diff --git a/src/Umbraco.Tests/Migrations/Upgrades/SqlServerUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/SqlServerUpgradeTest.cs index 8b5fc13114..088ea5eedf 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/SqlServerUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/SqlServerUpgradeTest.cs @@ -1,5 +1,6 @@ using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; @@ -24,7 +25,7 @@ namespace Umbraco.Tests.Migrations.Upgrades public override UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase(@"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco", "System.Data.SqlClient", Mock.Of()); + return new UmbracoDatabase(@"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco", Constants.DatabaseProviders.SqlServer, Mock.Of()); } public override DatabaseProviders GetDatabaseProvider() diff --git a/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs index e735e2330c..a375eb6850 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using Moq; using NUnit.Framework; using SQLCE4Umbraco; +using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; @@ -70,8 +71,10 @@ namespace Umbraco.Tests.Migrations.Upgrades Resolution.Freeze(); //Create the Sql CE database - var engine = new SqlCeEngine(settings.ConnectionString); - engine.CreateDatabase(); + using (var engine = new SqlCeEngine(settings.ConnectionString)) + { + engine.CreateDatabase(); + } SqlSyntaxContext.SqlSyntaxProvider = new SqlCeSyntaxProvider(); } @@ -101,7 +104,7 @@ namespace Umbraco.Tests.Migrations.Upgrades public UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Mock.Of()); + return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", Constants.DatabaseProviders.SqlCe, Mock.Of()); } public string GetDatabaseSpecificSqlScript() diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 6bc61649a5..4244188697 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -382,7 +383,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(content); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } /*[Test] diff --git a/src/Umbraco.Tests/Models/ContentTypeTests.cs b/src/Umbraco.Tests/Models/ContentTypeTests.cs index 0589d7d173..9d368ef886 100644 --- a/src/Umbraco.Tests/Models/ContentTypeTests.cs +++ b/src/Umbraco.Tests/Models/ContentTypeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core; @@ -285,7 +286,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(contentType); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } [Test] @@ -390,7 +391,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(contentType); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } [Test] @@ -498,7 +499,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(contentType); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs b/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs index d066fbacaf..f5c37220fd 100644 --- a/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs +++ b/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -75,7 +76,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(dtd); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } diff --git a/src/Umbraco.Tests/Models/DictionaryItemTests.cs b/src/Umbraco.Tests/Models/DictionaryItemTests.cs index ff57b5eec6..ab60ccd709 100644 --- a/src/Umbraco.Tests/Models/DictionaryItemTests.cs +++ b/src/Umbraco.Tests/Models/DictionaryItemTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; @@ -130,7 +131,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs b/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs index 46d6ea1022..c69047d94e 100644 --- a/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs +++ b/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; @@ -74,7 +75,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/LanguageTests.cs b/src/Umbraco.Tests/Models/LanguageTests.cs index 5d79364fb5..dc6b7bf989 100644 --- a/src/Umbraco.Tests/Models/LanguageTests.cs +++ b/src/Umbraco.Tests/Models/LanguageTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -56,7 +57,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index a8c15e9a5e..8119753c8b 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -212,7 +213,12 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(p.Alias, pDto.Alias); Assert.AreEqual(p.Id, pDto.Id); - Assert.IsTrue(p.Value == null ? pDto.Value == string.Empty : pDto.Value == p.Value); + if (p.Value == null) + Assert.AreEqual(pDto.Value, string.Empty); + else if (p.Value is decimal) + Assert.AreEqual(pDto.Value, ((decimal) p.Value).ToString(NumberFormatInfo.InvariantInfo)); + else + Assert.AreEqual(pDto.Value, p.Value.ToString()); } private void AssertProperty(ContentItemBasic result, Property p) diff --git a/src/Umbraco.Tests/Models/MemberGroupTests.cs b/src/Umbraco.Tests/Models/MemberGroupTests.cs index d39ddc93f3..ed208540ff 100644 --- a/src/Umbraco.Tests/Models/MemberGroupTests.cs +++ b/src/Umbraco.Tests/Models/MemberGroupTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -66,7 +67,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(group); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } diff --git a/src/Umbraco.Tests/Models/MemberTests.cs b/src/Umbraco.Tests/Models/MemberTests.cs index 62e3602232..ff1847d8ea 100644 --- a/src/Umbraco.Tests/Models/MemberTests.cs +++ b/src/Umbraco.Tests/Models/MemberTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; @@ -150,7 +151,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(member); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/PropertyGroupTests.cs b/src/Umbraco.Tests/Models/PropertyGroupTests.cs index ec345051ec..683b9b2adf 100644 --- a/src/Umbraco.Tests/Models/PropertyGroupTests.cs +++ b/src/Umbraco.Tests/Models/PropertyGroupTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -136,7 +137,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(pg); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/PropertyTypeTests.cs b/src/Umbraco.Tests/Models/PropertyTypeTests.cs index 96f28980ab..82c7521554 100644 --- a/src/Umbraco.Tests/Models/PropertyTypeTests.cs +++ b/src/Umbraco.Tests/Models/PropertyTypeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -80,7 +81,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(pt); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } diff --git a/src/Umbraco.Tests/Models/RelationTests.cs b/src/Umbraco.Tests/Models/RelationTests.cs index fdc3ae874b..e1d218ef6e 100644 --- a/src/Umbraco.Tests/Models/RelationTests.cs +++ b/src/Umbraco.Tests/Models/RelationTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -65,7 +66,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/RelationTypeTests.cs b/src/Umbraco.Tests/Models/RelationTypeTests.cs index 2022ab912d..526dfdb3f6 100644 --- a/src/Umbraco.Tests/Models/RelationTypeTests.cs +++ b/src/Umbraco.Tests/Models/RelationTypeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -59,7 +60,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/StylesheetTests.cs b/src/Umbraco.Tests/Models/StylesheetTests.cs index 21e339f54b..ec6761e0e2 100644 --- a/src/Umbraco.Tests/Models/StylesheetTests.cs +++ b/src/Umbraco.Tests/Models/StylesheetTests.cs @@ -1,14 +1,22 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Models { [TestFixture] public class StylesheetTests { + [SetUp] + public virtual void Initialize() + { + SettingsForTests.Reset(); + } + [Test] public void Can_Create_Stylesheet() { @@ -55,7 +63,10 @@ namespace Umbraco.Tests.Models public void Can_Update_Property() { // Arrange - var stylesheet = new Stylesheet("/css/styles.css") { Content = @"body { color:#000; } /**umb_name:Hello*/p{font-size:2em;} .bold {font-weight:bold;}" }; + var stylesheet = new Stylesheet("/css/styles.css") + { + Content = @"body { color:#000; } /**umb_name:Hello*/p{font-size:2em;} .bold {font-weight:bold;}" + }; var prop = stylesheet.Properties.Single(); prop.Alias = "li"; @@ -102,7 +113,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(stylesheet); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/TaskTests.cs b/src/Umbraco.Tests/Models/TaskTests.cs index 377746cbaf..54b62fcfa9 100644 --- a/src/Umbraco.Tests/Models/TaskTests.cs +++ b/src/Umbraco.Tests/Models/TaskTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -70,7 +71,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } diff --git a/src/Umbraco.Tests/Models/TaskTypeTests.cs b/src/Umbraco.Tests/Models/TaskTypeTests.cs index e83f8dc3cf..26d3a5d3dd 100644 --- a/src/Umbraco.Tests/Models/TaskTypeTests.cs +++ b/src/Umbraco.Tests/Models/TaskTypeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -55,7 +56,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/TemplateTests.cs b/src/Umbraco.Tests/Models/TemplateTests.cs index a810d9420f..e279851b77 100644 --- a/src/Umbraco.Tests/Models/TemplateTests.cs +++ b/src/Umbraco.Tests/Models/TemplateTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Reflection; using NUnit.Framework; @@ -75,7 +76,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } diff --git a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs index 52513d551d..651e955f33 100644 --- a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs +++ b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -133,7 +134,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/UserTests.cs b/src/Umbraco.Tests/Models/UserTests.cs index c686a78dc8..189a4a17a8 100644 --- a/src/Umbraco.Tests/Models/UserTests.cs +++ b/src/Umbraco.Tests/Models/UserTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models.Membership; @@ -108,7 +109,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/UserTypeTests.cs b/src/Umbraco.Tests/Models/UserTypeTests.cs index 01e4d6fd89..72aa0b2efc 100644 --- a/src/Umbraco.Tests/Models/UserTypeTests.cs +++ b/src/Umbraco.Tests/Models/UserTypeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models.Membership; @@ -54,7 +55,7 @@ namespace Umbraco.Tests.Models var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); - Console.WriteLine(json); + Debug.Print(json); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs b/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs index e9b58ffcae..bed5582609 100644 --- a/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs @@ -52,7 +52,7 @@ namespace Umbraco.Tests.Persistence var dbContext = new DatabaseContext( new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, _logger), - _logger, SqlSyntaxProvider, "System.Data.SqlServerCe.4.0"); + _logger, SqlSyntaxProvider, Constants.DatabaseProviders.SqlCe); var repositoryFactory = new RepositoryFactory(cacheHelper, _logger, SqlSyntaxProvider, SettingsForTests.GenerateMockSettings()); diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index 5883562f3b..94f0010201 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -25,7 +25,7 @@ namespace Umbraco.Tests.Persistence { _dbContext = new DatabaseContext( new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, Mock.Of()), - Mock.Of(), new SqlCeSyntaxProvider(), "System.Data.SqlServerCe.4.0"); + Mock.Of(), new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe); //unfortunately we have to set this up because the PetaPocoExtensions require singleton access ApplicationContext.Current = new ApplicationContext( @@ -81,10 +81,13 @@ namespace Umbraco.Tests.Persistence //by default the conn string is: Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1; //we'll just replace the sdf file with our custom one: //Create the Sql CE database - var engine = new SqlCeEngine(settings.ConnectionString.Replace("UmbracoPetaPocoTests", "DatabaseContextTests")); - engine.CreateDatabase(); + var connString = settings.ConnectionString.Replace("UmbracoPetaPocoTests", "DatabaseContextTests"); + using (var engine = new SqlCeEngine(connString)) + { + engine.CreateDatabase(); + } - var dbFactory = new DefaultDatabaseFactory(engine.LocalConnectionString, "System.Data.SqlServerCe.4.0", Mock.Of()); + var dbFactory = new DefaultDatabaseFactory(connString, Constants.DatabaseProviders.SqlCe, Mock.Of()); //re-map the dbcontext to the new conn string _dbContext = new DatabaseContext( dbFactory, diff --git a/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs b/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs index 841db8198c..6d5f0987eb 100644 --- a/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs +++ b/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs @@ -1,6 +1,7 @@ using System.Data.SqlClient; using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; @@ -13,7 +14,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling public void PetaPocoConnection_Cant_Connect_To_SqlDatabase_With_Invalid_User() { // Arrange - const string providerName = "System.Data.SqlClient"; + const string providerName = Constants.DatabaseProviders.SqlServer; const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=x;password=umbraco"; var factory = new DefaultDatabaseFactory(connectionString, providerName, Mock.Of()); var database = factory.CreateDatabase(); @@ -27,7 +28,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling public void PetaPocoConnection_Cant_Connect_To_SqlDatabase_Because_Of_Network() { // Arrange - const string providerName = "System.Data.SqlClient"; + const string providerName = Constants.DatabaseProviders.SqlServer; const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco"; var factory = new DefaultDatabaseFactory(connectionString, providerName, Mock.Of()); var database = factory.CreateDatabase(); diff --git a/src/Umbraco.Tests/Persistence/MySqlTableByTableTest.cs b/src/Umbraco.Tests/Persistence/MySqlTableByTableTest.cs index 17d12de478..4b3b9ef51d 100644 --- a/src/Umbraco.Tests/Persistence/MySqlTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/MySqlTableByTableTest.cs @@ -32,7 +32,7 @@ namespace Umbraco.Tests.Persistence base.Initialize(); _database = new Database("Server = 169.254.120.3; Database = testdb; Uid = umbraco; Pwd = umbraco", - "MySql.Data.MySqlClient"); + Constants.DatabaseProviders.MySql); } [TearDown] diff --git a/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs b/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs index d33148adc7..0a15994785 100644 --- a/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs +++ b/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using System.Threading; @@ -46,7 +47,7 @@ namespace Umbraco.Tests.Persistence double totalBytes1; IEnumerable keys; - Console.Write(Database.PocoData.PrintDebugCacheReport(out totalBytes1, out keys)); + Debug.Print(Database.PocoData.PrintDebugCacheReport(out totalBytes1, out keys)); result.Add(new Tuple>(totalBytes1, keys.Count(), keys)); } @@ -54,14 +55,14 @@ namespace Umbraco.Tests.Persistence for (int index = 0; index < result.Count; index++) { var tuple = result[index]; - Console.WriteLine("Bytes: {0}, Delegates: {1}", tuple.Item1, tuple.Item2); + Debug.Print("Bytes: {0}, Delegates: {1}", tuple.Item1, tuple.Item2); if (index != 0) { - Console.WriteLine("----------------DIFFERENCE---------------------"); + Debug.Print("----------------DIFFERENCE---------------------"); var diff = tuple.Item3.Except(result[index - 1].Item3); foreach (var d in diff) { - Console.WriteLine(d); + Debug.Print(d); } } @@ -87,13 +88,13 @@ namespace Umbraco.Tests.Persistence QueryStuff(id1, id2, id3, alias); var count1 = managedCache.GetCache().GetCount(); - Console.WriteLine("Keys = " + count1); + Debug.Print("Keys = " + count1); Assert.Greater(count1, 0); Thread.Sleep(10000); var count2 = managedCache.GetCache().GetCount(); - Console.WriteLine("Keys = " + count2); + Debug.Print("Keys = " + count2); Assert.Less(count2, count1); } diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs index 6e6d5251a8..c015410dfa 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -42,7 +43,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -79,7 +80,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -97,7 +98,7 @@ namespace Umbraco.Tests.Persistence.Querying .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972")) .Where("([umbracoNode].[id] = @0)", 1050) .Where("([cmsContentVersion].[VersionId] = @0)", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07")) - .OrderBy("[cmsContentVersion].[VersionDate] DESC"); + .OrderBy("([cmsContentVersion].[VersionDate]) DESC"); var sql = new Sql(); sql.Select("*") @@ -120,7 +121,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -151,7 +152,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs index 5768f81242..8619692c52 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -43,7 +44,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -81,7 +82,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -103,7 +104,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -127,7 +128,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -157,7 +158,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs index 445b5b3e36..eede4adaa4 100644 --- a/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -36,7 +37,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs index 2f6c903d14..4529343811 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq.Expressions; using Moq; using NUnit.Framework; @@ -23,7 +24,7 @@ namespace Umbraco.Tests.Persistence.Querying // var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); // var result = modelToSqlExpressionHelper.Visit(predicate); - // Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + // Debug.Print("Model to Sql ExpressionHelper: \n" + result); // Assert.AreEqual("[cmsContentType].[alias] = @0", result); // Assert.AreEqual("Test", modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -37,7 +38,7 @@ namespace Umbraco.Tests.Persistence.Querying var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); var result = modelToSqlExpressionHelper.Visit(predicate); - Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + Debug.Print("Model to Sql ExpressionHelper: \n" + result); Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result); Assert.AreEqual("-1%", modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -51,7 +52,7 @@ namespace Umbraco.Tests.Persistence.Querying var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); var result = modelToSqlExpressionHelper.Visit(predicate); - Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + Debug.Print("Model to Sql ExpressionHelper: \n" + result); Assert.AreEqual("([umbracoNode].[parentID] = @0)", result); Assert.AreEqual(-1, modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -64,7 +65,7 @@ namespace Umbraco.Tests.Persistence.Querying var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); var result = modelToSqlExpressionHelper.Visit(predicate); - Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + Debug.Print("Model to Sql ExpressionHelper: \n" + result); Assert.AreEqual("([umbracoUser].[userLogin] = @0)", result); Assert.AreEqual("hello@world.com", modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -77,7 +78,7 @@ namespace Umbraco.Tests.Persistence.Querying var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); var result = modelToSqlExpressionHelper.Visit(predicate); - Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + Debug.Print("Model to Sql ExpressionHelper: \n" + result); Assert.AreEqual("upper([umbracoUser].[userLogin]) = upper(@0)", result); Assert.AreEqual("hello@world.com", modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -93,7 +94,7 @@ namespace Umbraco.Tests.Persistence.Querying var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); var result = modelToSqlExpressionHelper.Visit(predicate); - Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); + Debug.Print("Model to Sql ExpressionHelper: \n" + result); Assert.AreEqual("upper(`umbracoUser`.`userLogin`) = upper(@0)", result); Assert.AreEqual("mydomain\\myuser", modelToSqlExpressionHelper.GetSqlParameters()[0]); @@ -110,11 +111,26 @@ namespace Umbraco.Tests.Persistence.Querying var modelToSqlExpressionHelper = new PocoToSqlExpressionHelper(); var result = modelToSqlExpressionHelper.Visit(predicate); - Console.WriteLine("Poco to Sql ExpressionHelper: \n" + result); + Debug.Print("Poco to Sql ExpressionHelper: \n" + result); Assert.AreEqual("upper(`umbracoUser`.`userLogin`) LIKE upper(@0)", result); Assert.AreEqual("mydomain\\myuser%", modelToSqlExpressionHelper.GetSqlParameters()[0]); } + [Test] + public void Sql_Replace_Mapped() + { + Expression> predicate = user => user.Username.Replace("@world", "@test") == "hello@test.com"; + var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); + var result = modelToSqlExpressionHelper.Visit(predicate); + + Debug.Print("Model to Sql ExpressionHelper: \n" + result); + + Assert.AreEqual("(replace([umbracoUser].[userLogin], @1, @2) = @0)", result); + Assert.AreEqual("hello@test.com", modelToSqlExpressionHelper.GetSqlParameters()[0]); + Assert.AreEqual("@world", modelToSqlExpressionHelper.GetSqlParameters()[1]); + Assert.AreEqual("@test", modelToSqlExpressionHelper.GetSqlParameters()[2]); + } + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs index 57b2ef7503..37a12df6a3 100644 --- a/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -39,7 +40,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs index efed0e4e0f..bb417a6b94 100644 --- a/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -36,7 +37,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs b/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs index 7eed26c2d0..39f3f484d0 100644 --- a/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; @@ -176,7 +177,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -195,7 +196,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -209,7 +210,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -223,7 +224,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -237,7 +238,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } [Test] @@ -257,7 +258,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs index dcc1b298a9..54b9a150f9 100644 --- a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; @@ -38,7 +39,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(1, result.Arguments.Length); Assert.AreEqual("-1%", sql.Arguments[0]); - Console.WriteLine(strResult); + Debug.Print(strResult); } [Test] @@ -65,7 +66,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(1, result.Arguments.Length); Assert.AreEqual(-1, sql.Arguments[0]); - Console.WriteLine(strResult); + Debug.Print(strResult); } [Test] @@ -91,7 +92,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual(1, result.Arguments.Length); Assert.AreEqual("umbTextpage", sql.Arguments[0]); - Console.WriteLine(strResult); + Debug.Print(strResult); } [Test] @@ -121,7 +122,7 @@ namespace Umbraco.Tests.Persistence.Querying var strResult = result.SQL; // Assert - Console.WriteLine(strResult); + Debug.Print(strResult); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index d29b073df7..abcb5b3d6a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -21,6 +21,7 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using umbraco.editorControls.tinyMCE3; using umbraco.interfaces; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Tests.Persistence.Repositories @@ -43,6 +44,14 @@ namespace Umbraco.Tests.Persistence.Repositories base.TearDown(); } + private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out DataTypeDefinitionRepository dtdRepository) + { + TemplateRepository tr; + var ctRepository = CreateRepository(unitOfWork, out contentTypeRepository, out tr); + dtdRepository = new DataTypeDefinitionRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, contentTypeRepository); + return ctRepository; + } + private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository) { TemplateRepository tr; @@ -222,6 +231,67 @@ namespace Umbraco.Tests.Persistence.Repositories } } + /// + /// This test ensures that when property values using special database fields are saved, the actual data in the + /// object being stored is also transformed in the same way as the data being stored in the database is. + /// Before you would see that ex: a decimal value being saved as 100 or "100", would be that exact value in the + /// object, but the value saved to the database was actually 100.000000. + /// When querying the database for the value again - the value would then differ from what is in the object. + /// This caused inconsistencies between saving+publishing and simply saving and then publishing, due to the former + /// sending the non-transformed data directly on to publishing. + /// + [Test] + public void Property_Values_With_Special_DatabaseTypes_Are_Equal_Before_And_After_Being_Persisted() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + DataTypeDefinitionRepository dataTypeDefinitionRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository, out dataTypeDefinitionRepository)) + { + // Setup + var dtd = new DataTypeDefinition(-1, Constants.PropertyEditors.DecimalAlias) { Name = "test", DatabaseType = DataTypeDatabaseType.Decimal }; + dataTypeDefinitionRepository.AddOrUpdate(dtd); + unitOfWork.Commit(); + + const string decimalPropertyAlias = "decimalProperty"; + const string intPropertyAlias = "intProperty"; + const string dateTimePropertyAlias = "datetimeProperty"; + var dateValue = new DateTime(2016, 1, 6); + + var propertyTypeCollection = new PropertyTypeCollection( + new List + { + MockedPropertyTypes.CreateDecimalProperty(decimalPropertyAlias, "Decimal property", dtd.Id), + MockedPropertyTypes.CreateIntegerProperty(intPropertyAlias, "Integer property"), + MockedPropertyTypes.CreateDateTimeProperty(dateTimePropertyAlias, "DateTime property") + }); + var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage", propertyTypeCollection); + contentTypeRepository.AddOrUpdate(contentType); + unitOfWork.Commit(); + + // Int and decimal values are passed in as strings as they would be from the backoffice UI + var textpage = MockedContent.CreateSimpleContentWithSpecialDatabaseTypes(contentType, "test@umbraco.org", -1, "100", "150", dateValue); + + // Act + repository.AddOrUpdate(textpage); + unitOfWork.Commit(); + + // Assert + Assert.That(contentType.HasIdentity, Is.True); + Assert.That(textpage.HasIdentity, Is.True); + + var persistedTextpage = repository.Get(textpage.Id); + Assert.That(persistedTextpage.Name, Is.EqualTo(textpage.Name)); + Assert.AreEqual(100m, persistedTextpage.GetValue(decimalPropertyAlias)); + Assert.AreEqual(persistedTextpage.GetValue(decimalPropertyAlias), textpage.GetValue(decimalPropertyAlias)); + Assert.AreEqual(150, persistedTextpage.GetValue(intPropertyAlias)); + Assert.AreEqual(persistedTextpage.GetValue(intPropertyAlias), textpage.GetValue(intPropertyAlias)); + Assert.AreEqual(dateValue, persistedTextpage.GetValue(dateTimePropertyAlias)); + Assert.AreEqual(persistedTextpage.GetValue(dateTimePropertyAlias), textpage.GetValue(dateTimePropertyAlias)); + } + } + [Test] public void Ensures_Permissions_Are_Set_If_Parent_Entity_Permissions_Exist() { @@ -266,17 +336,18 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) { ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); - Content textpage = MockedContent.CreateSimpleContent(contentType); + IContent textpage = MockedContent.CreateSimpleContent(contentType); // Act contentTypeRepository.AddOrUpdate(contentType); repository.AddOrUpdate(textpage); unitOfWork.Commit(); - + // Assert Assert.That(contentType.HasIdentity, Is.True); Assert.That(textpage.HasIdentity, Is.True); + } } @@ -305,9 +376,13 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(textpage); unitOfWork.Commit(); + var fetched = repository.Get(textpage.Id); + // Assert Assert.That(textpage.Template, Is.Not.Null); Assert.That(textpage.Template, Is.EqualTo(contentType.DefaultTemplate)); + + TestHelper.AssertAllPropertyValuesAreEquals(textpage, fetched, "yyyy-MM-dd HH:mm:ss"); } } @@ -685,8 +760,10 @@ namespace Umbraco.Tests.Persistence.Repositories { // Act var query = Query.Builder.Where(x => x.Level == 2); + var filterQuery = Query.Builder.Where(x => x.Name.Contains("Page 2")); + long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, "Page 2"); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, filterQuery); // Assert Assert.That(totalRecords, Is.EqualTo(1)); @@ -706,8 +783,10 @@ namespace Umbraco.Tests.Persistence.Repositories { // Act var query = Query.Builder.Where(x => x.Level == 2); + var filterQuery = Query.Builder.Where(x => x.Name.Contains("Page")); + long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, "Page"); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, filterQuery); // Assert Assert.That(totalRecords, Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index b2fed80f1b..7d8d7659c8 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -266,6 +266,8 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(contentType); unitOfWork.Commit(); + var fetched = repository.Get(contentType.Id); + // Assert Assert.That(contentType.HasIdentity, Is.True); Assert.That(contentType.PropertyGroups.All(x => x.HasIdentity), Is.True); @@ -281,6 +283,8 @@ namespace Umbraco.Tests.Persistence.Repositories { Assert.AreNotEqual(propertyType.Key, Guid.Empty); } + + TestHelper.AssertAllPropertyValuesAreEquals(contentType, fetched, "yyyy-MM-dd HH:mm:ss", ignoreProperties: new [] { "DefaultTemplate", "AllowedTemplates", "UpdateDate" }); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index 2f1c31487c..4db63f6085 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -355,9 +355,13 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Commit(); var exists = repository.Exists(dataTypeDefinition.Id); + var fetched = repository.Get(dataTypeDefinition.Id); + // Assert Assert.That(dataTypeDefinition.HasIdentity, Is.True); Assert.That(exists, Is.True); + + TestHelper.AssertAllPropertyValuesAreEquals(dataTypeDefinition, fetched, "yyyy-MM-dd HH:mm:ss"); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 10256e7dee..a216eaaa45 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -127,9 +127,13 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(image); unitOfWork.Commit(); + var fetched = repository.Get(image.Id); + // Assert Assert.That(mediaType.HasIdentity, Is.True); Assert.That(image.HasIdentity, Is.True); + + TestHelper.AssertAllPropertyValuesAreEquals(image, fetched, "yyyy-MM-dd HH:mm:ss"); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index eaedef09e4..b9ff045c71 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -197,11 +197,15 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(contentType); unitOfWork.Commit(); + var fetched = repository.Get(contentType.Id); + // Assert Assert.That(contentType.HasIdentity, Is.True); Assert.That(contentType.PropertyGroups.All(x => x.HasIdentity), Is.True); Assert.That(contentType.Path.Contains(","), Is.True); - Assert.That(contentType.SortOrder, Is.GreaterThan(0)); + Assert.That(contentType.SortOrder, Is.GreaterThan(0)); + + TestHelper.AssertAllPropertyValuesAreEquals(contentType, fetched, "yyyy-MM-dd HH:mm:ss", ignoreProperties: new[] { "UpdateDate" }); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 0d3b4e4e47..bb13f055c9 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Xml.Linq; using Moq; @@ -220,7 +221,9 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(sut.Name, Is.EqualTo("Johnny Hefty")); Assert.That(sut.Email, Is.EqualTo("johnny@example.com")); Assert.That(sut.RawPasswordValue, Is.EqualTo("123")); - Assert.That(sut.Username, Is.EqualTo("hefty")); + Assert.That(sut.Username, Is.EqualTo("hefty")); + + TestHelper.AssertAllPropertyValuesAreEquals(sut, member, "yyyy-MM-dd HH:mm:ss"); } } @@ -331,7 +334,7 @@ namespace Umbraco.Tests.Persistence.Repositories .OrderByDescending(x => x.VersionDate) .OrderBy(x => x.SortOrder); - Console.WriteLine(sql.SQL); + Debug.Print(sql.SQL); Assert.That(sql.SQL, Is.Not.Empty); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index aa4027d8a8..32341fac10 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -42,7 +42,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { - var memberType = MockedContentTypes.CreateSimpleMemberType(); + var memberType = (IMemberType)MockedContentTypes.CreateSimpleMemberType(); repository.AddOrUpdate(memberType); unitOfWork.Commit(); @@ -56,6 +56,33 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(sut.PropertyGroups.Any(x => x.HasIdentity == false || x.Id == 0), Is.False); Assert.That(sut.PropertyTypes.Any(x => x.HasIdentity == false || x.Id == 0), Is.False); + + TestHelper.AssertAllPropertyValuesAreEquals(sut, memberType, "yyyy-MM-dd HH:mm:ss"); + } + } + + [Test] + public void Can_Persist_Member_Type_Same_Property_Keys() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var memberType = (IMemberType)MockedContentTypes.CreateSimpleMemberType(); + + repository.AddOrUpdate(memberType); + unitOfWork.Commit(); + + var propertyKeys = memberType.PropertyTypes.Select(x => x.Key).OrderBy(x => x).ToArray(); + var groupKeys = memberType.PropertyGroups.Select(x => x.Key).OrderBy(x => x).ToArray(); + + memberType = repository.Get(memberType.Id); + var propertyKeys2 = memberType.PropertyTypes.Select(x => x.Key).OrderBy(x => x).ToArray(); + var groupKeys2 = memberType.PropertyGroups.Select(x => x.Key).OrderBy(x => x).ToArray(); + + Assert.IsTrue(propertyKeys.SequenceEqual(propertyKeys2)); + Assert.IsTrue(groupKeys.SequenceEqual(groupKeys2)); + } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs b/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs new file mode 100644 index 0000000000..084d43d24c --- /dev/null +++ b/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs @@ -0,0 +1,229 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Persistence.Repositories +{ + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture] + public class RedirectUrlRepositoryTests : BaseDatabaseFactoryTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + CreateTestData(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + [Test] + public void CanSaveAndGet() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + + using (var uow = provider.GetUnitOfWork()) + using (var repo = CreateRepository(uow)) + { + var rurl = new RedirectUrl + { + ContentKey = _textpage.Key, + Url = "blah" + }; + repo.AddOrUpdate(rurl); + uow.Commit(); + + Assert.AreNotEqual(0, rurl.Id); + } + + using (var uow = provider.GetUnitOfWork()) + using (var repo = CreateRepository(uow)) + { + var rurl = repo.GetMostRecentUrl("blah"); + uow.Commit(); + + Assert.IsNotNull(rurl); + Assert.AreEqual(_textpage.Id, rurl.ContentId); + } + } + + [Test] + public void CanSaveAndGetMostRecent() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + + Assert.AreNotEqual(_textpage.Id, _otherpage.Id); + + using (var uow = provider.GetUnitOfWork()) + using (var repo = CreateRepository(uow)) + { + var rurl = new RedirectUrl + { + ContentKey = _textpage.Key, + Url = "blah" + }; + repo.AddOrUpdate(rurl); + uow.Commit(); + + Assert.AreNotEqual(0, rurl.Id); + + // fixme - too fast = same date = key violation? + // and... can that happen in real life? + // we don't really *care* about the IX, only supposed to make things faster... + // BUT in realife we AddOrUpdate in a trx so it should be safe, always + + rurl = new RedirectUrl + { + ContentKey = _otherpage.Key, + Url = "blah", + CreateDateUtc = rurl.CreateDateUtc.AddSeconds(1) // ensure time difference + }; + repo.AddOrUpdate(rurl); + uow.Commit(); + + Assert.AreNotEqual(0, rurl.Id); + } + + using (var uow = provider.GetUnitOfWork()) + using (var repo = CreateRepository(uow)) + { + var rurl = repo.GetMostRecentUrl("blah"); + uow.Commit(); + + Assert.IsNotNull(rurl); + Assert.AreEqual(_otherpage.Id, rurl.ContentId); + } + } + + [Test] + public void CanSaveAndGetByContent() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + + using (var uow = provider.GetUnitOfWork()) + using (var repo = CreateRepository(uow)) + { + var rurl = new RedirectUrl + { + ContentKey = _textpage.Key, + Url = "blah" + }; + repo.AddOrUpdate(rurl); + uow.Commit(); + + Assert.AreNotEqual(0, rurl.Id); + + // fixme - goes too fast and bam, errors, first is blah + + rurl = new RedirectUrl + { + ContentKey = _textpage.Key, + Url = "durg", + CreateDateUtc = rurl.CreateDateUtc.AddSeconds(1) // ensure time difference + }; + repo.AddOrUpdate(rurl); + uow.Commit(); + + Assert.AreNotEqual(0, rurl.Id); + } + + using (var uow = provider.GetUnitOfWork()) + using (var repo = CreateRepository(uow)) + { + var rurls = repo.GetContentUrls(_textpage.Key).ToArray(); + uow.Commit(); + + Assert.AreEqual(2, rurls.Length); + Assert.AreEqual("durg", rurls[0].Url); + Assert.AreEqual("blah", rurls[1].Url); + } + } + + [Test] + public void CanSaveAndDelete() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + + using (var uow = provider.GetUnitOfWork()) + using (var repo = CreateRepository(uow)) + { + var rurl = new RedirectUrl + { + ContentKey = _textpage.Key, + Url = "blah" + }; + repo.AddOrUpdate(rurl); + uow.Commit(); + + Assert.AreNotEqual(0, rurl.Id); + + rurl = new RedirectUrl + { + ContentKey = _otherpage.Key, + Url = "durg" + }; + repo.AddOrUpdate(rurl); + uow.Commit(); + + Assert.AreNotEqual(0, rurl.Id); + } + + using (var uow = provider.GetUnitOfWork()) + using (var repo = CreateRepository(uow)) + { + repo.DeleteContentUrls(_textpage.Key); + uow.Commit(); + + var rurls = repo.GetContentUrls(_textpage.Key); + + Assert.AreEqual(0, rurls.Count()); + } + } + + private IRedirectUrlRepository CreateRepository(IDatabaseUnitOfWork uow) + { + return new RedirectUrlRepository(uow, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax); + } + + private IContent _textpage, _subpage, _otherpage, _trashed; + + public void CreateTestData() + { + //Create and Save ContentType "umbTextpage" -> (NodeDto.NodeIdSeed) + var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); + contentType.Key = Guid.NewGuid(); + ServiceContext.ContentTypeService.Save(contentType); + + //Create and Save Content "Homepage" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 1) + _textpage = MockedContent.CreateSimpleContent(contentType); + _textpage.Key = Guid.NewGuid(); + ServiceContext.ContentService.Save(_textpage); + + //Create and Save Content "Text Page 1" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 2) + _subpage = MockedContent.CreateSimpleContent(contentType, "Text Page 1", _textpage.Id); + _subpage.Key = Guid.NewGuid(); + ServiceContext.ContentService.Save(_subpage); + + //Create and Save Content "Text Page 1" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 3) + _otherpage = MockedContent.CreateSimpleContent(contentType, "Text Page 2", _textpage.Id); + _otherpage.Key = Guid.NewGuid(); + ServiceContext.ContentService.Save(_otherpage); + + //Create and Save Content "Text Page Deleted" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 4) + _trashed = MockedContent.CreateSimpleContent(contentType, "Text Page Deleted", -20); + _trashed.Key = Guid.NewGuid(); + ((Content) _trashed).Trashed = true; + ServiceContext.ContentService.Save(_trashed); + } + } +} diff --git a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs index d7115461db..474735979e 100644 --- a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs @@ -40,11 +40,13 @@ namespace Umbraco.Tests.Persistence } //Create the Sql CE database - var engine = new SqlCeEngine("Datasource=|DataDirectory|test.sdf;Flush Interval=1;"); - engine.CreateDatabase(); + using (var engine = new SqlCeEngine("Datasource=|DataDirectory|test.sdf;Flush Interval=1;")) + { + engine.CreateDatabase(); + } _database = new Database("Datasource=|DataDirectory|test.sdf;Flush Interval=1;", - "System.Data.SqlServerCe.4.0"); + Constants.DatabaseProviders.SqlCe); } [TearDown] diff --git a/src/Umbraco.Tests/Persistence/SqlTableByTableTest.cs b/src/Umbraco.Tests/Persistence/SqlTableByTableTest.cs index f781d38447..0b081628d1 100644 --- a/src/Umbraco.Tests/Persistence/SqlTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/SqlTableByTableTest.cs @@ -30,7 +30,7 @@ namespace Umbraco.Tests.Persistence base.Initialize(); _database = new Database(@"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco", - "System.Data.SqlClient"); + Constants.DatabaseProviders.SqlServer); } [TearDown] diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/MySqlSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/MySqlSyntaxProviderTests.cs index 67c29581a4..8126aa5e36 100644 --- a/src/Umbraco.Tests/Persistence/SyntaxProvider/MySqlSyntaxProviderTests.cs +++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/MySqlSyntaxProviderTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using Moq; using NUnit.Framework; @@ -34,16 +35,16 @@ namespace Umbraco.Tests.Persistence.SyntaxProvider var indexes = SqlSyntaxContext.SqlSyntaxProvider.Format(definition.Indexes); var keys = SqlSyntaxContext.SqlSyntaxProvider.Format(definition.ForeignKeys); - Console.WriteLine(create); - Console.WriteLine(primaryKey); + Debug.Print(create); + Debug.Print(primaryKey); foreach (var sql in keys) { - Console.WriteLine(sql); + Debug.Print(sql); } foreach (var sql in indexes) { - Console.WriteLine(sql); + Debug.Print(sql); } } diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs index 2ec7688a6a..e960f50799 100644 --- a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs +++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -57,16 +58,16 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, var indexes = sqlSyntax.Format(definition.Indexes); var keys = sqlSyntax.Format(definition.ForeignKeys); - Console.WriteLine(create); - Console.WriteLine(primaryKey); + Debug.Print(create); + Debug.Print(primaryKey); foreach (var sql in keys) { - Console.WriteLine(sql); + Debug.Print(sql); } foreach (var sql in indexes) { - Console.WriteLine(sql); + Debug.Print(sql); } } diff --git a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs index a8c779d093..ceb2326e6d 100644 --- a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs +++ b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs @@ -283,7 +283,7 @@ namespace Umbraco.Tests.Plugins { var trees = _manager.ResolveAttributedTrees(); // commit 6c5e35ec2cbfa31be6790d1228e0c2faf5f55bc8 brings the count down to 14 - Assert.AreEqual(14, trees.Count()); + Assert.AreEqual(12, trees.Count()); } [Test] diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs index 26f1665fe2..8fa332b108 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs @@ -41,8 +41,8 @@ namespace Umbraco.Tests.PropertyEditors var valueEditor = new PropertyValueEditor { - ValueType = "STRING" - }; + ValueType = PropertyEditorValueTypes.String + }; var result = valueEditor.ConvertDbToEditor(prop, prop.PropertyType, new Mock().Object); Assert.AreEqual(isOk, !(result is string)); @@ -73,7 +73,7 @@ namespace Umbraco.Tests.PropertyEditors { var valueEditor = new PropertyValueEditor { - ValueType = "DECIMAL" + ValueType = PropertyEditorValueTypes.Decimal }; var result = valueEditor.TryConvertValueToCrlType("12.34"); @@ -86,7 +86,7 @@ namespace Umbraco.Tests.PropertyEditors { var valueEditor = new PropertyValueEditor { - ValueType = "DECIMAL" + ValueType = PropertyEditorValueTypes.Decimal }; var result = valueEditor.TryConvertValueToCrlType("12,34"); @@ -99,7 +99,7 @@ namespace Umbraco.Tests.PropertyEditors { var valueEditor = new PropertyValueEditor { - ValueType = "DECIMAL" + ValueType = PropertyEditorValueTypes.Decimal }; var result = valueEditor.TryConvertValueToCrlType(string.Empty); @@ -112,20 +112,20 @@ namespace Umbraco.Tests.PropertyEditors { var valueEditor = new PropertyValueEditor { - ValueType = "DATE" - }; + ValueType = PropertyEditorValueTypes.Date + }; var result = valueEditor.TryConvertValueToCrlType("2010-02-05"); Assert.IsTrue(result.Success); Assert.AreEqual(new DateTime(2010, 2, 5), result.Result); } - [TestCase("STRING", "hello", "hello")] - [TestCase("TEXT", "hello", "hello")] - [TestCase("INT", 123, "123")] - [TestCase("INTEGER", 123, "123")] - [TestCase("INTEGER", "", "")] //test empty string for int - [TestCase("DATETIME", "", "")] //test empty string for date + [TestCase(PropertyEditorValueTypes.String, "hello", "hello")] + [TestCase(PropertyEditorValueTypes.Text, "hello", "hello")] + [TestCase(PropertyEditorValueTypes.Integer, 123, "123")] + [TestCase(PropertyEditorValueTypes.IntegerAlternative, 123, "123")] + [TestCase(PropertyEditorValueTypes.Integer, "", "")] //test empty string for int + [TestCase(PropertyEditorValueTypes.DateTime, "", "")] //test empty string for date public void Value_Editor_Can_Serialize_Value(string valueType, object val, string expected) { var prop = new Property(1, Guid.NewGuid(), new PropertyType("test", DataTypeDatabaseType.Nvarchar), val); @@ -145,8 +145,8 @@ namespace Umbraco.Tests.PropertyEditors var value = 12.34M; var valueEditor = new PropertyValueEditor { - ValueType = "DECIMAL" - }; + ValueType = PropertyEditorValueTypes.Decimal + }; var prop = new Property(1, Guid.NewGuid(), new PropertyType("test", DataTypeDatabaseType.Decimal), value); @@ -159,8 +159,8 @@ namespace Umbraco.Tests.PropertyEditors { var valueEditor = new PropertyValueEditor { - ValueType = "DECIMAL" - }; + ValueType = PropertyEditorValueTypes.Decimal + }; var prop = new Property(1, Guid.NewGuid(), new PropertyType("test", DataTypeDatabaseType.Decimal), string.Empty); @@ -174,8 +174,8 @@ namespace Umbraco.Tests.PropertyEditors var now = DateTime.Now; var valueEditor = new PropertyValueEditor { - ValueType = "DATE" - }; + ValueType = PropertyEditorValueTypes.Date + }; var prop = new Property(1, Guid.NewGuid(), new PropertyType("test", DataTypeDatabaseType.Date), now); diff --git a/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs index 7b0ffb41d7..8a83bea75a 100644 --- a/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs @@ -7,14 +7,17 @@ using NUnit.Framework; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.UmbracoExamine; using umbraco.MacroEngines; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Tests.PublishedContent { public class LegacyExamineBackedMediaTests : ExamineBaseTest { - public override void TestSetup() + public override void Initialize() { - base.TestSetup(); + base.Initialize(); var settings = SettingsForTests.GenerateMockSettings(); var contentMock = Mock.Get(settings.Content); @@ -22,13 +25,8 @@ namespace Umbraco.Tests.PublishedContent contentMock.Setup(x => x.UmbracoLibraryCacheDuration).Returns(1800); SettingsForTests.ConfigureSettings(settings); } - - public override void TestTearDown() - { - SettingsForTests.Reset(); - base.TestTearDown(); - } - + + [Test] public void Ensure_Children_Are_Sorted() { @@ -47,7 +45,9 @@ namespace Umbraco.Tests.PublishedContent var children = backedMedia.ChildrenAsList.Value; var currSort = 0; - for (var i = 0; i < children.Count(); i++) + Assert.Greater(children.Count, 0); + + for (var i = 0; i < children.Count; i++) { Assert.GreaterOrEqual(children[i].SortOrder, currSort); currSort = children[i].SortOrder; diff --git a/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs b/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs index 475909318a..be9928004b 100644 --- a/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs +++ b/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs @@ -57,10 +57,10 @@ namespace Umbraco.Tests.Routing public const int LangNlId = 337; public const int LangDkId = 338; - protected override void SetupApplicationContext() + protected override ApplicationContext CreateApplicationContext() { var settings = SettingsForTests.GetDefault(); - ApplicationContext.Current = new ApplicationContext( + return new ApplicationContext( new DatabaseContext(Mock.Of(), Logger, Mock.Of(), "test"), GetServiceContext(settings, Logger), CacheHelper, diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index 316d957a0c..9ae9519697 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -112,7 +112,7 @@ namespace Umbraco.Tests.Services watch.Stop(); var elapsed = watch.ElapsedMilliseconds; - Console.WriteLine("100 content items saved in {0} ms", elapsed); + Debug.Print("100 content items saved in {0} ms", elapsed); // Assert Assert.That(pages.Any(x => x.HasIdentity == false), Is.False); @@ -131,7 +131,7 @@ namespace Umbraco.Tests.Services watch.Stop(); var elapsed = watch.ElapsedMilliseconds; - Console.WriteLine("100 content items saved in {0} ms", elapsed); + Debug.Print("100 content items saved in {0} ms", elapsed); // Assert Assert.That(pages.Any(x => x.HasIdentity == false), Is.False); @@ -159,7 +159,7 @@ namespace Umbraco.Tests.Services watch.Stop(); var elapsed = watch.ElapsedMilliseconds; - Console.WriteLine("100 content items retrieved in {0} ms without caching", elapsed); + Debug.Print("100 content items retrieved in {0} ms without caching", elapsed); // Assert Assert.That(contents.Any(x => x.HasIdentity == false), Is.False); @@ -190,7 +190,7 @@ namespace Umbraco.Tests.Services watch.Stop(); var elapsed = watch.ElapsedMilliseconds; - Console.WriteLine("1000 content items retrieved in {0} ms without caching", elapsed); + Debug.Print("1000 content items retrieved in {0} ms without caching", elapsed); // Assert //Assert.That(contents.Any(x => x.HasIdentity == false), Is.False); @@ -223,7 +223,7 @@ namespace Umbraco.Tests.Services watch.Stop(); var elapsed = watch.ElapsedMilliseconds; - Console.WriteLine("100 content items retrieved in {0} ms with caching", elapsed); + Debug.Print("100 content items retrieved in {0} ms with caching", elapsed); // Assert Assert.That(contentsCached.Any(x => x.HasIdentity == false), Is.False); @@ -256,7 +256,7 @@ namespace Umbraco.Tests.Services watch.Stop(); var elapsed = watch.ElapsedMilliseconds; - Console.WriteLine("1000 content items retrieved in {0} ms with caching", elapsed); + Debug.Print("1000 content items retrieved in {0} ms with caching", elapsed); // Assert //Assert.That(contentsCached.Any(x => x.HasIdentity == false), Is.False); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 8d86e806a6..70188eaa9d 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -14,6 +16,7 @@ using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Publishing; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -876,6 +879,46 @@ namespace Umbraco.Tests.Services Assert.That(content.Published, Is.True); } + [Test] + public void Can_Publish_Content_WithEvents() + { + ContentService.Publishing += ContentServiceOnPublishing; + + // tests that during 'publishing' event, what we get from the repo is the 'old' content, + // because 'publishing' fires before the 'saved' event ie before the content is actually + // saved + + try + { + var contentService = ServiceContext.ContentService; + var content = contentService.GetById(NodeDto.NodeIdSeed + 1); + Assert.AreEqual("Home", content.Name); + + content.Name = "foo"; + var published = contentService.Publish(content, 0); + + Assert.That(published, Is.True); + Assert.That(content.Published, Is.True); + + var e = ServiceContext.ContentService.GetById(content.Id); + Assert.AreEqual("foo", e.Name); + } + finally + { + ContentService.Publishing -= ContentServiceOnPublishing; + } + } + + private void ContentServiceOnPublishing(IPublishingStrategy sender, PublishEventArgs args) + { + Assert.AreEqual(1, args.PublishedEntities.Count()); + var entity = args.PublishedEntities.First(); + Assert.AreEqual("foo", entity.Name); + + var e = ServiceContext.ContentService.GetById(entity.Id); + Assert.AreEqual("Home", e.Name); + } + [Test] public void Can_Publish_Only_Valid_Content() { @@ -1587,7 +1630,7 @@ namespace Umbraco.Tests.Services list.Add(content); list.AddRange(CreateChildrenOf(contentType, content, 4)); - Console.WriteLine("Created: 'Hierarchy Simple Text Page {0}'", i); + Debug.Print("Created: 'Hierarchy Simple Text Page {0}'", i); } return list; @@ -1601,7 +1644,7 @@ namespace Umbraco.Tests.Services var c = MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Subpage " + i, content); list.Add(c); - Console.WriteLine("Created: 'Hierarchy Simple Text Subpage {0}' - Depth: {1}", i, depth); + Debug.Print("Created: 'Hierarchy Simple Text Subpage {0}' - Depth: {1}", i, depth); } return list; } diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index 742205904f..bf752aa601 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -7,14 +7,14 @@ using Umbraco.Core; using Umbraco.Core.Exceptions; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; - +using Umbraco.Core.Services; using Umbraco.Tests.CodeFirst.TestModels.Composition; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Services { - + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] [TestFixture, RequiresSTA] public class ContentTypeServiceTests : BaseServiceTest @@ -275,6 +275,66 @@ namespace Umbraco.Tests.Services Assert.That(success, Is.False); } + [Test] + public void Deleting_ContentType_Sends_Correct_Number_Of_DeletedEntities_In_Events() + { + var cts = ServiceContext.ContentTypeService; + var deletedEntities = 0; + var contentType = MockedContentTypes.CreateSimpleContentType("page", "Page"); + cts.Save(contentType); + + ContentTypeService.DeletedContentType += (sender, args) => + { + deletedEntities += args.DeletedEntities.Count(); + }; + + cts.Delete(contentType); + + Assert.AreEqual(deletedEntities, 1); + } + + [Test] + public void Deleting_Multiple_ContentTypes_Sends_Correct_Number_Of_DeletedEntities_In_Events() + { + var cts = ServiceContext.ContentTypeService; + var deletedEntities = 0; + var contentType = MockedContentTypes.CreateSimpleContentType("page", "Page"); + cts.Save(contentType); + var contentType2 = MockedContentTypes.CreateSimpleContentType("otherPage", "Other page"); + cts.Save(contentType2); + + ContentTypeService.DeletedContentType += (sender, args) => + { + deletedEntities += args.DeletedEntities.Count(); + }; + + cts.Delete(contentType); + cts.Delete(contentType2); + + Assert.AreEqual(2, deletedEntities); + } + + [Test] + public void Deleting_ContentType_With_Child_Sends_Correct_Number_Of_DeletedEntities_In_Events() + { + var cts = ServiceContext.ContentTypeService; + var deletedEntities = 0; + var contentType = MockedContentTypes.CreateSimpleContentType("page", "Page"); + cts.Save(contentType); + var contentType2 = MockedContentTypes.CreateSimpleContentType("subPage", "Sub page"); + contentType2.ParentId = contentType.Id; + cts.Save(contentType2); + + ContentTypeService.DeletedContentType += (sender, args) => + { + deletedEntities += args.DeletedEntities.Count(); + }; + + cts.Delete(contentType); + + Assert.AreEqual(2, deletedEntities); + } + [Test] public void Can_Remove_ContentType_Composition_From_ContentType() { @@ -574,7 +634,7 @@ namespace Umbraco.Tests.Services * -- Content Page * ---- Advanced Page -> Content Meta * Content Meta :: Composition, has 'Title' - * + * * Content Meta has 'Title' PropertyType * Adding 'Title' to BasePage should fail */ @@ -605,7 +665,7 @@ namespace Umbraco.Tests.Services }; var authorAdded = contentPage.AddPropertyType(authorPropertyType, "Content"); service.Save(contentPage); - + var compositionAdded = advancedPage.AddContentType(contentMetaComposition); service.Save(advancedPage); @@ -764,7 +824,7 @@ namespace Umbraco.Tests.Services }; var titleAdded = seoComposition.AddPropertyType(titlePropertyType, "Content"); service.Save(seoComposition); - + var seoCompositionAdded = advancedPage.AddContentType(seoComposition); var metaCompositionAdded = moreAdvancedPage.AddContentType(metaComposition); service.Save(advancedPage); @@ -871,7 +931,7 @@ namespace Umbraco.Tests.Services var subtitleAdded = contentPage.AddPropertyType(subtitlePropertyType, "Content"); var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext, "author") { - Name = "Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 + Name = "Author", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 }; var authorAdded = advancedPage.AddPropertyType(authorPropertyType, "Content"); service.Save(basePage); @@ -945,7 +1005,7 @@ namespace Umbraco.Tests.Services public void Can_Rename_PropertyGroup_With_Inherited_PropertyGroups() { //Related the first issue in screencast from this post http://issues.umbraco.org/issue/U4-5986 - + // Arrange var service = ServiceContext.ContentTypeService; @@ -1218,8 +1278,8 @@ namespace Umbraco.Tests.Services * - Content Page * -- Advanced Page * Content Meta :: Composition - */ - + */ + // Arrange var service = ServiceContext.ContentTypeService; var basePage = MockedContentTypes.CreateBasicContentType(); diff --git a/src/Umbraco.Tests/Services/MediaServiceTests.cs b/src/Umbraco.Tests/Services/MediaServiceTests.cs index 12d4b4b0aa..a53257b26a 100644 --- a/src/Umbraco.Tests/Services/MediaServiceTests.cs +++ b/src/Umbraco.Tests/Services/MediaServiceTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; @@ -97,6 +98,40 @@ namespace Umbraco.Tests.Services } + [Test] + public void Can_Get_Media_By_Path() + { + var mediaService = ServiceContext.MediaService; + var mediaType = MockedContentTypes.CreateImageMediaType("Image2"); + ServiceContext.ContentTypeService.Save(mediaType); + + var media = MockedMedia.CreateMediaImage(mediaType, -1); + mediaService.Save(media); + + var mediaPath = "/media/test-image.png"; + var resolvedMedia = mediaService.GetMediaByPath(mediaPath); + + Assert.IsNotNull(resolvedMedia); + Assert.That(resolvedMedia.GetValue(Constants.Conventions.Media.File).ToString() == mediaPath); + } + + [Test] + public void Can_Get_Media_With_Crop_By_Path() + { + var mediaService = ServiceContext.MediaService; + var mediaType = MockedContentTypes.CreateImageMediaType("Image2"); + ServiceContext.ContentTypeService.Save(mediaType); + + var media = MockedMedia.CreateMediaImageWithCrop(mediaType, -1); + mediaService.Save(media); + + var mediaPath = "/media/test-image.png"; + var resolvedMedia = mediaService.GetMediaByPath(mediaPath); + + Assert.IsNotNull(resolvedMedia); + Assert.That(resolvedMedia.GetValue(Constants.Conventions.Media.File).ToString().Contains(mediaPath)); + } + private Tuple CreateTrashedTestMedia() { //Create and Save folder-Media -> 1050 diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 9e2b440815..c9cf4e6d5b 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; @@ -33,6 +34,34 @@ namespace Umbraco.Tests.Services base.TearDown(); } + [Test] + public void Can_Create_Member() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); + ServiceContext.MemberService.Save(member); + + Assert.AreNotEqual(0, member.Id); + var foundMember = ServiceContext.MemberService.GetById(member.Id); + Assert.IsNotNull(foundMember); + Assert.AreEqual("test@test.com", foundMember.Email); + } + + [Test] + public void Can_Create_Member_With_Long_TLD_In_Email() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.marketing", "pass", "test"); + ServiceContext.MemberService.Save(member); + + Assert.AreNotEqual(0, member.Id); + var foundMember = ServiceContext.MemberService.GetById(member.Id); + Assert.IsNotNull(foundMember); + Assert.AreEqual("test@test.marketing", foundMember.Email); + } + [Test] public void Can_Create_Role() { diff --git a/src/Umbraco.Tests/Services/PackagingServiceTests.cs b/src/Umbraco.Tests/Services/PackagingServiceTests.cs index 72b1323e65..05ae7d0ad4 100644 --- a/src/Umbraco.Tests/Services/PackagingServiceTests.cs +++ b/src/Umbraco.Tests/Services/PackagingServiceTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Xml.Linq; @@ -42,7 +43,7 @@ namespace Umbraco.Tests.Services Assert.That(element, Is.Not.Null); Assert.That(element.Element("name").Value, Is.EqualTo("Test")); Assert.That(element.Element("alias").Value, Is.EqualTo("test1")); - Console.Write(element.ToString()); + Debug.Print(element.ToString()); } [Test] diff --git a/src/Umbraco.Tests/Services/PerformanceTests.cs b/src/Umbraco.Tests/Services/PerformanceTests.cs index 2a4ce8b5ea..69f39392d6 100644 --- a/src/Umbraco.Tests/Services/PerformanceTests.cs +++ b/src/Umbraco.Tests/Services/PerformanceTests.cs @@ -44,7 +44,7 @@ namespace Umbraco.Tests.Services protected override string GetDbProviderName() { - return "System.Data.SqlClient"; + return Constants.DatabaseProviders.SqlServer; } diff --git a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs index ed87493c01..00e744c28b 100644 --- a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs +++ b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Services //it is multi-threaded. _dbFactory = new PerThreadDatabaseFactory(Logger); //overwrite the local object - ApplicationContext.DatabaseContext = new DatabaseContext(_dbFactory, Logger, new SqlCeSyntaxProvider(), "System.Data.SqlServerCe.4.0"); + ApplicationContext.DatabaseContext = new DatabaseContext(_dbFactory, Logger, new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe); //disable cache var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); diff --git a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs index ec7bbe3c72..58b0451995 100644 --- a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs +++ b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using System.Text; @@ -369,9 +370,9 @@ namespace Umbraco.Tests.Strings // then next string element is one char and 3 bytes, 16 bits of code point Assert.AreEqual('t', bytes[9]); //foreach (var b in bytes) - // Console.WriteLine("{0:X}", b); + // Debug.Print("{0:X}", b); - Console.WriteLine("\U00010B70"); + Debug.Print("\U00010B70"); } [Test] diff --git a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs index 07ac002a48..199bd9254d 100644 --- a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Globalization; using NUnit.Framework; using Umbraco.Core; @@ -31,8 +32,8 @@ namespace Umbraco.Tests.Strings [TestCase("hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", "hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", true)] public void String_To_Guid(string first, string second, bool result) { - Console.WriteLine("First: " + first.ToGuid()); - Console.WriteLine("Second: " + second.ToGuid()); + Debug.Print("First: " + first.ToGuid()); + Debug.Print("Second: " + second.ToGuid()); Assert.AreEqual(result, first.ToGuid() == second.ToGuid()); } diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 967998e517..28649457dd 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -1,22 +1,16 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; using System.Configuration; using System.Data.SqlServerCe; using System.IO; -using System.Linq; using System.Web.Routing; using System.Xml; -using Moq; using NUnit.Framework; using SQLCE4Umbraco; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; @@ -27,7 +21,6 @@ using Umbraco.Core.Services; using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.XmlPublishedCache; -using Umbraco.Web.Routing; using Umbraco.Web.Security; using umbraco.BusinessLogic; using Umbraco.Core.Events; @@ -35,9 +28,10 @@ using Umbraco.Core.Events; namespace Umbraco.Tests.TestHelpers { /// - /// Use this abstract class for tests that requires a Sql Ce database populated with the umbraco db schema. - /// The PetaPoco Database class should be used through the . + /// Provides a base class for Umbraco application tests that require a database. /// + /// Can provide a SqlCE database populated with the Umbraco schema. The database should be accessed + /// through the . [TestFixture, RequiresSTA] public abstract class BaseDatabaseFactoryTest : BaseUmbracoApplicationTest { @@ -48,15 +42,16 @@ namespace Umbraco.Tests.TestHelpers private bool _firstTestInFixture = true; //Used to flag if its the first test in the current session - private bool _isFirstRunInTestSession = false; + private bool _isFirstRunInTestSession; //Used to flag if its the first test in the current fixture - private bool _isFirstTestInFixture = false; + private bool _isFirstTestInFixture; private ApplicationContext _appContext; private string _dbPath; //used to store (globally) the pre-built db with schema and initial data - private static Byte[] _dbBytes; + private static byte[] _dbBytes; + private DefaultDatabaseFactory _dbFactory; [SetUp] public override void Initialize() @@ -66,27 +61,11 @@ namespace Umbraco.Tests.TestHelpers var path = TestHelper.CurrentAssemblyDirectory; AppDomain.CurrentDomain.SetData("DataDirectory", path); - //disable cache - var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); - - var dbFactory = new DefaultDatabaseFactory( + _dbFactory = new DefaultDatabaseFactory( GetDbConnectionString(), GetDbProviderName(), Logger); - - var repositoryFactory = new RepositoryFactory(cacheHelper, Logger, SqlSyntax, SettingsForTests.GenerateMockSettings()); - - var evtMsgs = new TransientMessagesFactory(); - _appContext = new ApplicationContext( - //assign the db context - new DatabaseContext(dbFactory, Logger, SqlSyntax, "System.Data.SqlServerCe.4.0"), - //assign the service context - new ServiceContext(repositoryFactory, new PetaPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, Logger), cacheHelper, Logger, evtMsgs), - cacheHelper, - ProfilingLogger) - { - IsReady = true - }; + _dbFactory.ResetForTests(); base.Initialize(); @@ -94,7 +73,7 @@ namespace Umbraco.Tests.TestHelpers { //TODO: Somehow make this faster - takes 5s + - DatabaseContext.Initialize(dbFactory.ProviderName, dbFactory.ConnectionString); + DatabaseContext.Initialize(_dbFactory.ProviderName, _dbFactory.ConnectionString); CreateSqlCeDatabase(); InitializeDatabase(); @@ -103,14 +82,27 @@ namespace Umbraco.Tests.TestHelpers } } - protected virtual ISqlSyntaxProvider SqlSyntax + protected override ApplicationContext CreateApplicationContext() { - get { return new SqlCeSyntaxProvider(); } + var repositoryFactory = new RepositoryFactory(CacheHelper, Logger, SqlSyntax, SettingsForTests.GenerateMockSettings()); + + var evtMsgs = new TransientMessagesFactory(); + _appContext = new ApplicationContext( + //assign the db context + new DatabaseContext(_dbFactory, Logger, SqlSyntax, GetDbProviderName()), + //assign the service context + new ServiceContext(repositoryFactory, new PetaPocoUnitOfWorkProvider(_dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, Logger), CacheHelper, Logger, evtMsgs), + CacheHelper, + ProfilingLogger) + { + IsReady = true + }; + return _appContext; } - protected override void SetupApplicationContext() + protected virtual ISqlSyntaxProvider SqlSyntax { - ApplicationContext.Current = _appContext; + get { return GetSyntaxProvider(); } } /// @@ -120,11 +112,16 @@ namespace Umbraco.Tests.TestHelpers { get { - var att = this.GetType().GetCustomAttribute(false); + var att = GetType().GetCustomAttribute(false); return att != null ? att.Behavior : DatabaseBehavior.NoDatabasePerFixture; } } + protected virtual ISqlSyntaxProvider GetSyntaxProvider() + { + return new SqlCeSyntaxProvider(); + } + protected virtual string GetDbProviderName() { return "System.Data.SqlServerCe.4.0"; @@ -167,7 +164,7 @@ namespace Umbraco.Tests.TestHelpers || (DatabaseTestBehavior == DatabaseBehavior.NewDbFileAndSchemaPerTest || DatabaseTestBehavior == DatabaseBehavior.EmptyDbFilePerTest) || (_isFirstTestInFixture && DatabaseTestBehavior == DatabaseBehavior.NewDbFileAndSchemaPerFixture)) { - + using (ProfilingLogger.TraceDuration("Remove database file")) { RemoveDatabaseFile(ex => @@ -188,8 +185,10 @@ namespace Umbraco.Tests.TestHelpers } else { - var engine = new SqlCeEngine(settings.ConnectionString); - engine.CreateDatabase(); + using (var engine = new SqlCeEngine(settings.ConnectionString)) + { + engine.CreateDatabase(); + } } } @@ -247,7 +246,7 @@ namespace Umbraco.Tests.TestHelpers //Create the umbraco database and its base data schemaHelper.CreateDatabaseSchema(false, ApplicationContext); - //close the connections, we're gonna read this baby in as a byte array so we don't have to re-initialize the + //close the connections, we're gonna read this baby in as a byte array so we don't have to re-initialize the // damn db for each test CloseDbConnections(); @@ -283,7 +282,7 @@ namespace Umbraco.Tests.TestHelpers private void CloseDbConnections() { - //Ensure that any database connections from a previous test is disposed. + //Ensure that any database connections from a previous test is disposed. //This is really just double safety as its also done in the TearDown. if (ApplicationContext != null && DatabaseContext != null && DatabaseContext.Database != null) DatabaseContext.Database.Dispose(); @@ -305,23 +304,21 @@ namespace Umbraco.Tests.TestHelpers } } } - if (_firstTestInFixture) + if (_firstTestInFixture == false) return; + + lock (Locker) { - lock (Locker) - { - if (_firstTestInFixture) - { - _isFirstTestInFixture = true; //set the flag - _firstTestInFixture = false; - } - } + if (_firstTestInFixture == false) return; + + _isFirstTestInFixture = true; //set the flag + _firstTestInFixture = false; } } private void RemoveDatabaseFile(Action onFail = null) { CloseDbConnections(); - string path = TestHelper.CurrentAssemblyDirectory; + var path = TestHelper.CurrentAssemblyDirectory; try { string filePath = string.Concat(path, "\\UmbracoPetaPocoTests.sdf"); @@ -336,9 +333,7 @@ namespace Umbraco.Tests.TestHelpers //We will swallow this exception! That's because a sub class might require further teardown logic. if (onFail != null) - { onFail(ex); - } } } @@ -396,7 +391,7 @@ namespace Umbraco.Tests.TestHelpers protected virtual string GetXmlContent(int templateId) { return @" - @@ -409,7 +404,7 @@ namespace Umbraco.Tests.TestHelpers 1 This is some content]]> - + diff --git a/src/Umbraco.Tests/TestHelpers/BaseSeleniumTest.cs b/src/Umbraco.Tests/TestHelpers/BaseSeleniumTest.cs index e31f962122..ef039630f3 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseSeleniumTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseSeleniumTest.cs @@ -51,14 +51,16 @@ namespace Umbraco.Tests.TestHelpers var connectionString = string.Format(@"Data Source={0}", databaseDataPath); //Create the Sql CE database - var engine = new SqlCeEngine(connectionString); - if (File.Exists(databaseDataPath) == false) - engine.CreateDatabase(); + using (var engine = new SqlCeEngine(connectionString)) + { + if (File.Exists(databaseDataPath) == false) + engine.CreateDatabase(); + } var syntaxProvider = new SqlCeSyntaxProvider(); SqlSyntaxContext.SqlSyntaxProvider = syntaxProvider; - _database = new UmbracoDatabase(connectionString, "System.Data.SqlServerCe.4.0", Mock.Of()); + _database = new UmbracoDatabase(connectionString, Constants.DatabaseProviders.SqlCe, Mock.Of()); // First remove anything in the database var creation = new DatabaseSchemaCreation(_database, Mock.Of(), syntaxProvider); diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index abf1edd3a5..642bad4a82 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -1,5 +1,4 @@ -using System.Collections; -using System.Collections.Generic; +using System; using System.IO; using System.Reflection; using AutoMapper; @@ -9,7 +8,6 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models.Mapping; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Profiling; @@ -20,18 +18,16 @@ using Umbraco.Web; using Umbraco.Web.Models.Mapping; using umbraco.BusinessLogic; using Umbraco.Core.Events; -using ObjectExtensions = Umbraco.Core.ObjectExtensions; namespace Umbraco.Tests.TestHelpers { /// - /// A base test class used for umbraco tests whcih sets up the logging, plugin manager any base resolvers, etc... and - /// ensures everything is torn down properly. + /// Provides a base class for Umbraco application tests. /// + /// Sets logging, pluging manager, application context, base resolvers... [TestFixture] public abstract class BaseUmbracoApplicationTest : BaseUmbracoConfigurationTest { - [TestFixtureSetUp] public void InitializeFixture() { @@ -65,16 +61,18 @@ namespace Umbraco.Tests.TestHelpers { base.TearDown(); - //reset settings + // reset settings SettingsForTests.Reset(); UmbracoContext.Current = null; TestHelper.CleanContentDirectories(); TestHelper.CleanUmbracoSettingsConfig(); - //reset the app context, this should reset most things that require resetting like ALL resolvers + + // reset the app context, this should reset most things that require resetting like ALL resolvers ApplicationContext.Current.DisposeIfDisposable(); ApplicationContext.Current = null; - ResetPluginManager(); + // reset plugin manager + ResetPluginManager(); } private static readonly object Locker = new object(); @@ -85,7 +83,7 @@ namespace Umbraco.Tests.TestHelpers { if (LegacyPropertyEditorIdToAliasConverter.Count() == 0) { - //Create the legacy prop-eds mapping + // create the legacy prop-eds mapping LegacyPropertyEditorIdToAliasConverter.CreateMappingsForCoreEditors(); } } @@ -100,7 +98,7 @@ namespace Umbraco.Tests.TestHelpers /// private void InitializeMappers() { - if (this.GetType().GetCustomAttribute(false) != null) + if (GetType().GetCustomAttribute(false) != null) { Mapper.Initialize(configuration => { @@ -121,7 +119,7 @@ namespace Umbraco.Tests.TestHelpers /// /// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan /// all of the assemblies. Inheritors can override this if plugin manager resetting is required, generally needs - /// to be set to true if the SetupPluginManager has been overridden. + /// to be set to true if the SetupPluginManager has been overridden. /// protected virtual bool PluginManagerResetRequired { @@ -141,7 +139,12 @@ namespace Umbraco.Tests.TestHelpers protected virtual void SetupCacheHelper() { - CacheHelper = CacheHelper.CreateDisabledCacheHelper(); + CacheHelper = CreateCacheHelper(); + } + + protected virtual CacheHelper CreateCacheHelper() + { + return CacheHelper.CreateDisabledCacheHelper(); } /// @@ -149,15 +152,19 @@ namespace Umbraco.Tests.TestHelpers /// protected virtual void SetupApplicationContext() { + var applicationContext = CreateApplicationContext(); + ApplicationContext.Current = applicationContext; + } + protected virtual ApplicationContext CreateApplicationContext() + { var sqlSyntax = new SqlCeSyntaxProvider(); - var repoFactory = new RepositoryFactory(CacheHelper.CreateDisabledCacheHelper(), Logger, sqlSyntax, SettingsForTests.GenerateMockSettings()); + var repoFactory = new RepositoryFactory(CacheHelper, Logger, sqlSyntax, SettingsForTests.GenerateMockSettings()); var evtMsgs = new TransientMessagesFactory(); - ApplicationContext.Current = new ApplicationContext( + var applicationContext = new ApplicationContext( //assign the db context - new DatabaseContext(new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, Logger), - Logger, sqlSyntax, "System.Data.SqlServerCe.4.0"), + new DatabaseContext(new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, Logger), Logger, sqlSyntax, Constants.DatabaseProviders.SqlCe), //assign the service context new ServiceContext(repoFactory, new PetaPocoUnitOfWorkProvider(Logger), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, Logger), CacheHelper, Logger, evtMsgs), CacheHelper, @@ -165,6 +172,7 @@ namespace Umbraco.Tests.TestHelpers { IsReady = true }; + return applicationContext; } /// @@ -211,7 +219,9 @@ namespace Umbraco.Tests.TestHelpers { get { return ProfilingLogger.Logger; } } + protected ProfilingLogger ProfilingLogger { get; private set; } + protected CacheHelper CacheHelper { get; private set; } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs index 3452b55f39..db6919b24e 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs @@ -79,6 +79,21 @@ namespace Umbraco.Tests.TestHelpers.Entities return content; } + public static Content CreateSimpleContentWithSpecialDatabaseTypes(IContentType contentType, string name, int parentId, string decimalValue, string intValue, DateTime datetimeValue) + { + var content = new Content(name, parentId, contentType) { Language = "en-US", CreatorId = 0, WriterId = 0 }; + object obj = new + { + decimalProperty = decimalValue, + intProperty = intValue, + datetimeProperty = datetimeValue + }; + + content.PropertyValues(obj); + content.ResetDirtyProperties(false); + return content; + } + public static Content CreateAllTypesContent(IContentType contentType, string name, int parentId) { var content = new Content("Random Content Name", parentId, contentType) { Language = "en-US", Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 }; diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index e3585d0554..ef9371c203 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -412,7 +412,7 @@ namespace Umbraco.Tests.TestHelpers.Entities contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeDefinitionId = -90 }); mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs index 1db17509e1..a4ff2d1e77 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs @@ -5,37 +5,53 @@ namespace Umbraco.Tests.TestHelpers.Entities { public static class MockedMedia { - public static IMedia CreateMediaImage(IMediaType mediaType, int parentId) - { - var media = new Media("Test Image", parentId, mediaType) - { - CreatorId = 0 - }; + public static IMedia CreateMediaImage(IMediaType mediaType, int parentId) + { + var media = new Media("Test Image", parentId, mediaType) + { + CreatorId = 0 + }; - media.SetValue(Constants.Conventions.Media.File, "/media/test-image.png"); - media.SetValue(Constants.Conventions.Media.Width, "200"); - media.SetValue(Constants.Conventions.Media.Height, "200"); - media.SetValue(Constants.Conventions.Media.Bytes, "100"); - media.SetValue(Constants.Conventions.Media.Extension, "png"); + media.SetValue(Constants.Conventions.Media.File, "/media/test-image.png"); + media.SetValue(Constants.Conventions.Media.Width, "200"); + media.SetValue(Constants.Conventions.Media.Height, "200"); + media.SetValue(Constants.Conventions.Media.Bytes, "100"); + media.SetValue(Constants.Conventions.Media.Extension, "png"); - return media; - } + return media; + } - public static IMedia CreateMediaFile(IMediaType mediaType, int parentId) - { - var media = new Media("Test File", parentId, mediaType) - { - CreatorId = 0 - }; + public static IMedia CreateMediaFile(IMediaType mediaType, int parentId) + { + var media = new Media("Test File", parentId, mediaType) + { + CreatorId = 0 + }; - media.SetValue(Constants.Conventions.Media.File, "/media/test-file.txt"); - media.SetValue(Constants.Conventions.Media.Bytes, "4"); - media.SetValue(Constants.Conventions.Media.Extension, "txt"); + media.SetValue(Constants.Conventions.Media.File, "/media/test-file.txt"); + media.SetValue(Constants.Conventions.Media.Bytes, "4"); + media.SetValue(Constants.Conventions.Media.Extension, "txt"); - return media; - } + return media; + } - public static IMedia CreateMediaFolder(IMediaType mediaType, int parentId) + public static IMedia CreateMediaImageWithCrop(IMediaType mediaType, int parentId) + { + var media = new Media("Test Image", parentId, mediaType) + { + CreatorId = 0 + }; + + media.SetValue(Constants.Conventions.Media.File, "{src: '/media/test-image.png', crops: []}"); + media.SetValue(Constants.Conventions.Media.Width, "200"); + media.SetValue(Constants.Conventions.Media.Height, "200"); + media.SetValue(Constants.Conventions.Media.Bytes, "100"); + media.SetValue(Constants.Conventions.Media.Extension, "png"); + + return media; + } + + public static IMedia CreateMediaFolder(IMediaType mediaType, int parentId) { var media = new Media("Test Folder", parentId, mediaType) { diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedPropertyTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedPropertyTypes.cs new file mode 100644 index 0000000000..b60afe77c9 --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedPropertyTypes.cs @@ -0,0 +1,66 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Tests.TestHelpers.Entities +{ + public class MockedPropertyTypes + { + /// + /// Returns a decimal property. + /// Requires a datatype definition Id, since this is not one of the pre-populated datatypes + /// + /// Alias of the created property type + /// Name of the created property type + /// Integer Id of a decimal datatype to use + /// Property type storing decimal value + public static PropertyType CreateDecimalProperty(string alias, string name, int dtdId) + { + return + new PropertyType("test", DataTypeDatabaseType.Decimal, alias) + { + Name = name, + Description = "Decimal property type", + Mandatory = false, + SortOrder = 4, + DataTypeDefinitionId = dtdId + }; + } + + /// + /// Returns a integer property. + /// + /// Alias of the created property type + /// Name of the created property type + /// Property type storing integer value + public static PropertyType CreateIntegerProperty(string alias, string name) + { + return + new PropertyType("test", DataTypeDatabaseType.Integer, alias) + { + Name = name, + Description = "Integer property type", + Mandatory = false, + SortOrder = 4, + DataTypeDefinitionId = -51 + }; + } + + /// + /// Returns a DateTime property. + /// + /// Alias of the created property type + /// Name of the created property type + /// Property type storing DateTime value + public static PropertyType CreateDateTimeProperty(string alias, string name) + { + return + new PropertyType("test", DataTypeDatabaseType.Date, alias) + { + Name = name, + Description = "DateTime property type", + Mandatory = false, + SortOrder = 4, + DataTypeDefinitionId = -36 + }; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index eec71f9a7f..5e362ee1f8 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -1,13 +1,17 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Configuration; using System.IO; using System.Linq; using System.Reflection; +using NUnit.Framework; using SqlCE4Umbraco; using Umbraco.Core; using Umbraco.Core.IO; using umbraco.DataLayer; +using Umbraco.Core.Models.EntityBase; using GlobalSettings = umbraco.GlobalSettings; namespace Umbraco.Tests.TestHelpers @@ -116,6 +120,83 @@ namespace Umbraco.Tests.TestHelpers File.Delete(umbracoSettingsFile); } + public static void AssertAllPropertyValuesAreEquals(object actual, object expected, string dateTimeFormat = null, Func sorter = null, string[] ignoreProperties = null) + { + var properties = expected.GetType().GetProperties(); + foreach (var property in properties) + { + //ignore properties that are attributed with this + var att = property.GetCustomAttribute(false); + if (att != null && att.State == EditorBrowsableState.Never) + continue; - } + if (ignoreProperties != null && ignoreProperties.Contains(property.Name)) + continue; + + var expectedValue = property.GetValue(expected, null); + var actualValue = property.GetValue(actual, null); + + if (((actualValue is string) == false) && actualValue is IEnumerable) + { + AssertListsAreEquals(property, (IEnumerable)actualValue, (IEnumerable)expectedValue, dateTimeFormat, sorter); + } + else if (dateTimeFormat.IsNullOrWhiteSpace() == false && actualValue is DateTime) + { + Assert.AreEqual(((DateTime) expectedValue).ToString(dateTimeFormat), ((DateTime)actualValue).ToString(dateTimeFormat), "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expectedValue, actualValue); + } + else + { + Assert.AreEqual(expectedValue, actualValue, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expectedValue, actualValue); + } + } + } + + private static void AssertListsAreEquals(PropertyInfo property, IEnumerable actualList, IEnumerable expectedList, string dateTimeFormat, Func sorter) + { + if (sorter == null) + { + //this is pretty hackerific but saves us some code to write + sorter = enumerable => + { + //semi-generic way of ensuring any collection of IEntity are sorted by Ids for comparison + var entities = enumerable.OfType().ToList(); + if (entities.Count > 0) + { + return entities.OrderBy(x => x.Id); + } + else + { + return enumerable; + } + }; + } + + var actualListEx = sorter(actualList).Cast().ToList(); + var expectedListEx = sorter(expectedList).Cast().ToList(); + + if (actualListEx.Count != expectedListEx.Count) + Assert.Fail("Collection {0}.{1} does not match. Expected IEnumerable containing {2} elements but was IEnumerable containing {3} elements", property.PropertyType.Name, property.Name, expectedListEx.Count, actualListEx.Count); + + for (int i = 0; i < actualListEx.Count; i++) + { + var actualValue = actualListEx[i]; + var expectedValue = expectedListEx[i]; + + if (((actualValue is string) == false) && actualValue is IEnumerable) + { + AssertListsAreEquals(property, (IEnumerable)actualValue, (IEnumerable)expectedValue, dateTimeFormat, sorter); + } + else if (dateTimeFormat.IsNullOrWhiteSpace() == false && actualValue is DateTime) + { + Assert.AreEqual(((DateTime)expectedValue).ToString(dateTimeFormat), ((DateTime)actualValue).ToString(dateTimeFormat), "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expectedValue, actualValue); + } + else + { + Assert.AreEqual(expectedValue, actualValue, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expectedValue, actualValue); + } + } + } + + + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/TryConvertToTests.cs b/src/Umbraco.Tests/TryConvertToTests.cs new file mode 100644 index 0000000000..7116d98038 --- /dev/null +++ b/src/Umbraco.Tests/TryConvertToTests.cs @@ -0,0 +1,105 @@ +using System; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Strings; +using Umbraco.Tests.TestHelpers; + +namespace Umbraco.Tests +{ + [TestFixture] + public class TryConvertToTests + { + [SetUp] + public void SetUp() + { + var settings = SettingsForTests.GetDefault(); + ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(settings).WithDefaultConfig()); + Resolution.Freeze(); + } + + [TearDown] + public void TearDown() + { + ShortStringHelperResolver.Reset(); + } + + [Test] + public void ConvertToIntegerTest() + { + var conv = "100".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100, conv.Result); + + conv = "100.000".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100, conv.Result); + + conv = "100,000".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100, conv.Result); + + // oops + conv = "100.001".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100, conv.Result); + + conv = 100m.TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100, conv.Result); + + conv = 100.000m.TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100, conv.Result); + + // oops + conv = 100.001m.TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100, conv.Result); + } + + [Test] + public void ConvertToDecimalTest() + { + var conv = "100".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100m, conv.Result); + + conv = "100.000".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100m, conv.Result); + + conv = "100,000".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100m, conv.Result); + + conv = "100.001".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100.001m, conv.Result); + + conv = 100m.TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100m, conv.Result); + + conv = 100.000m.TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100m, conv.Result); + + conv = 100.001m.TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100.001m, conv.Result); + + conv = 100.TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(100m, conv.Result); + } + + [Test] + public void ConvertToDateTimeTest() + { + var conv = "2016-06-07".TryConvertTo(); + Assert.IsTrue(conv); + Assert.AreEqual(new DateTime(2016, 6, 7), conv.Result); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index bb87abc83b..a38e951434 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -59,8 +59,8 @@ ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll True - - ..\packages\Examine.0.1.68.0\lib\Examine.dll + + ..\packages\Examine.0.1.69.0\lib\Examine.dll True @@ -175,7 +175,11 @@ + + + + diff --git a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs index d01b298bb4..6bd01c7f1c 100644 --- a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs @@ -45,19 +45,21 @@ namespace Umbraco.Tests.UmbracoExamine private static UmbracoContentIndexer _indexer; private Lucene.Net.Store.Directory _luceneDir; - public override void TestSetup() - { - base.TestSetup(); - _luceneDir = new RAMDirectory(); - _indexer = IndexInitializer.GetUmbracoIndexer(_luceneDir); - _indexer.RebuildIndex(); - _searcher = IndexInitializer.GetUmbracoSearcher(_luceneDir); - } + public override void Initialize() + { + base.Initialize(); - public override void TestTearDown() - { - base.TestTearDown(); - _luceneDir.Dispose(); - } + _luceneDir = new RAMDirectory(); + _indexer = IndexInitializer.GetUmbracoIndexer(_luceneDir); + _indexer.RebuildIndex(); + _searcher = IndexInitializer.GetUmbracoSearcher(_luceneDir); + } + + public override void TearDown() + { + base.TearDown(); + _luceneDir.Dispose(); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs index 5fed998194..ec3babc7ab 100644 --- a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs @@ -1,5 +1,9 @@ -using NUnit.Framework; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; using UmbracoExamine; @@ -7,29 +11,27 @@ using UmbracoExamine; namespace Umbraco.Tests.UmbracoExamine { [TestFixture] - public abstract class ExamineBaseTest : BaseUmbracoConfigurationTest + public abstract class ExamineBaseTest : BaseDatabaseFactoryTest { - - [SetUp] - public virtual void TestSetup() + /// + /// sets up resolvers before resolution is frozen + /// + protected override void FreezeResolution() { UmbracoExamineSearcher.DisableInitializationCheck = true; BaseUmbracoIndexer.DisableInitializationCheck = true; ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(SettingsForTests.GetDefault())); - Resolution.Freeze(); + base.FreezeResolution(); } - [TearDown] - public virtual void TestTearDown() + public override void TearDown() { + base.TearDown(); + UmbracoExamineSearcher.DisableInitializationCheck = null; BaseUmbracoIndexer.DisableInitializationCheck = null; - - //reset all resolvers - ResolverCollection.ResetAll(); - //reset resolution itself (though this should be taken care of by resetting any of the resolvers above) - Resolution.Reset(); } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 9990c58a43..b303eed997 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -10,6 +10,7 @@ using Moq; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Services; using UmbracoExamine; using UmbracoExamine.Config; @@ -41,7 +42,48 @@ namespace Umbraco.Tests.UmbracoExamine } if (contentService == null) { - contentService = Mock.Of(); + long longTotalRecs; + int intTotalRecs; + + var allRecs = dataService.ContentService.GetLatestContentByXPath("//*[@isDoc]") + .Root + .Elements() + .Select(x => Mock.Of( + m => + m.Id == (int)x.Attribute("id") && + m.ParentId == (int)x.Attribute("parentID") && + m.Level == (int)x.Attribute("level") && + m.CreatorId == 0 && + m.SortOrder == (int)x.Attribute("sortOrder") && + m.CreateDate == (DateTime)x.Attribute("createDate") && + m.UpdateDate == (DateTime)x.Attribute("updateDate") && + m.Name == (string)x.Attribute("nodeName") && + m.Path == (string)x.Attribute("path") && + m.Properties == new PropertyCollection() && + m.ContentType == Mock.Of(mt => + mt.Alias == x.Name.LocalName && + mt.Id == (int)x.Attribute("nodeType") && + mt.Icon == "test"))) + .ToArray(); + + + contentService = Mock.Of( + x => x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) + == + allRecs + && x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) + == + allRecs + && x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out intTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) + == + allRecs + && x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()) + == + allRecs); } if (userService == null) { @@ -86,7 +128,6 @@ namespace Umbraco.Tests.UmbracoExamine It.IsAny(), It.IsAny(), It.IsAny(), out intTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) == allRecs); - } if (dataTypeService == null) { diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs index a5418649fe..7c36dd2953 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs @@ -94,6 +94,7 @@ namespace Umbraco.Tests.UmbracoExamine } [Test] + [Ignore] public void Index_Move_Media_To_Non_Indexable_ParentID() { //get a node from the data repo (this one exists underneath 2222) @@ -142,8 +143,6 @@ namespace Umbraco.Tests.UmbracoExamine { var s = (IndexSearcher)_searcher.GetSearcher(); - - //first delete all 'Content' (not media). This is done by directly manipulating the index with the Lucene API, not examine! var contentTerm = new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content); @@ -177,6 +176,7 @@ namespace Umbraco.Tests.UmbracoExamine /// This will delete an item from the index and ensure that all children of the node are deleted too! /// [Test] + [Ignore] public void Index_Delete_Index_Item_Ensure_Heirarchy_Removed() { @@ -207,23 +207,24 @@ namespace Umbraco.Tests.UmbracoExamine private Lucene.Net.Store.Directory _luceneDir; - public override void TestTearDown() - { - base.TestTearDown(); - _luceneDir.Dispose(); + public override void TearDown() + { + base.TearDown(); + _luceneDir.Dispose(); UmbracoExamineSearcher.DisableInitializationCheck = null; BaseUmbracoIndexer.DisableInitializationCheck = null; - } - - public override void TestSetup() - { - base.TestSetup(); - _luceneDir = new RAMDirectory(); - _indexer = IndexInitializer.GetUmbracoIndexer(_luceneDir); - _indexer.RebuildIndex(); - _searcher = IndexInitializer.GetUmbracoSearcher(_luceneDir); - } + } + + public override void Initialize() + { + base.Initialize(); + _luceneDir = new RAMDirectory(); + _indexer = IndexInitializer.GetUmbracoIndexer(_luceneDir); + _indexer.RebuildIndex(); + _searcher = IndexInitializer.GetUmbracoSearcher(_luceneDir); + } + #endregion } diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index f7fa0f8492..cc0fc5f214 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/Umbraco.Tests/unit-test-log4net.CI.config b/src/Umbraco.Tests/unit-test-log4net.CI.config new file mode 100644 index 0000000000..d7035032ef --- /dev/null +++ b/src/Umbraco.Tests/unit-test-log4net.CI.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index ab78d10380..09cfa16609 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -20,12 +20,13 @@ "underscore": "~1.7.0", "rgrove-lazyload": "*", "bootstrap-social": "~4.8.0", - "jquery": "2.0.3", + "jquery": "2.2.4", "jquery-ui": "1.11.4", "angular-dynamic-locale": "0.1.28", "ng-file-upload": "~7.3.8", "tinymce": "~4.1.10", "codemirror": "~5.3.0", - "angular-local-storage": "~0.2.3" + "angular-local-storage": "~0.2.3", + "moment": "~2.10.3" } } diff --git a/src/Umbraco.Web.UI.Client/docs/src/api/index.ngdoc b/src/Umbraco.Web.UI.Client/docs/src/api/index.ngdoc index a7cf500744..74cc458b90 100644 --- a/src/Umbraco.Web.UI.Client/docs/src/api/index.ngdoc +++ b/src/Umbraco.Web.UI.Client/docs/src/api/index.ngdoc @@ -2,15 +2,9 @@ @name Umbraco 7 JS documentation @description -#Belle +#Umbraco Backoffice UI API documentation -Umbraco 7 UI, codename "Belle" Built on AngularJS, Lazyload.js and Twitter Bootstrap - -##Introduction -Slides from the initial demonstration of Belle done at the Umbraco DK Fest can be found here: - -http://rawgithub.com/umbraco/Belle/master/Presentation/index.html - +This documentation relates to the angular, js, css and less APIs for developing back office components ##Running the site with mocked data @@ -45,7 +39,7 @@ So run the command: sudo npm install grunt-cli -g -Now that you have node and grunt installed, you can open `/Umbraco.Belle.Client` in either `cmd.exe` or terminal and run: +Now that you have node and grunt installed, you can open `/Umbraco.Web.UI.Client` in either `cmd.exe` or terminal and run: grunt dev diff --git a/src/Umbraco.Web.UI.Client/docs/umb-docs.css b/src/Umbraco.Web.UI.Client/docs/umb-docs.css new file mode 100644 index 0000000000..eef0fcee39 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/docs/umb-docs.css @@ -0,0 +1,78 @@ + +html { + font-family: 'Segoe UI', Tahoma, Helvetica, sans-serif; +} +body { + font-family: 'Segoe UI', Tahoma, Helvetica, sans-serif; + +} + +.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { + font-family: inherit; + font-weight: 400; + line-height: 1.1; + color: inherit; +} + +.content code { + font-family: inherit; +} + +.navbar .brand { + display: block; + float: left; + padding: 10px 20px 10px; + margin-left: -20px; + font-size: 20px; + font-weight: 200; + color: rgba(0,0,0,.8); + text-shadow: none; +} + +.navbar-fixed-top .navbar-inner { + min-height: 50px; + background: #a3db78; +} + +.form-search .well { + background-color: #f5fbf1; +} + +.form-search > ul.nav > li.module { + background-color: #daf0c9; +} + +.breadcrumb { + background-color: #f5fbf1; +} + +a { + color: #f36f21; +} +a:hover { + text-decoration: none; + color: rgba(0,0,0,.8); +} +.nav-list > .active > a, .nav-list > .active > a:hover, .nav-list > .active > a:focus, +.form-search > .nav-list > .active > a, .form-search > .nav-list > .active > a:hover, .form-search > .nav-list > .active > a:focus { + color: #f36f21; + text-shadow: none; + background-color: inherit; +} + +.form-search > ul.nav > li > a { + color: rgba(0,0,0,.8); + text-shadow: none; +} + +.form-search > ul.nav > li > a:hover { + text-decoration: none; + background-color: inherit; + text-shadow: none; + color: #000; +} + +.header img { + width: 50px; + margin-top: 5px; +} diff --git a/src/Umbraco.Web.UI.Client/gruntFile.js b/src/Umbraco.Web.UI.Client/gruntFile.js index 0500e24c31..946966d358 100644 --- a/src/Umbraco.Web.UI.Client/gruntFile.js +++ b/src/Umbraco.Web.UI.Client/gruntFile.js @@ -380,8 +380,12 @@ module.exports = function (grunt) { options: { dest: 'docs/api', startPage: '/api', - title: "Umbraco 7", + title: "Umbraco Backoffice UI API Documentation", html5Mode: false, + styles: [ + 'docs/umb-docs.css' + ], + image: "https://our.umbraco.org/assets/images/logo.svg" }, api: { src: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'], @@ -390,7 +394,7 @@ module.exports = function (grunt) { tutorials: { src: [], title: '' - } + } }, eslint:{ @@ -459,6 +463,10 @@ module.exports = function (grunt) { expand: true, ignorePackages: ['bootstrap'], packageSpecific: { + 'moment': { + keepExpandedHierarchy: false, + files: ['min/moment-with-locales.js'] + }, 'typeahead.js': { keepExpandedHierarchy: false, files: ['dist/typeahead.bundle.min.js'] @@ -476,7 +484,8 @@ module.exports = function (grunt) { files: ['css/font-awesome.min.css', 'fonts/*'] }, "jquery": { - files: ['jquery.min.js', 'jquery.min.map'] + keepExpandedHierarchy: false, + files: ['dist/jquery.min.js', 'dist/jquery.min.map'] }, 'jquery-ui': { keepExpandedHierarchy: false, diff --git a/src/Umbraco.Web.UI.Client/lib/moment/moment-with-locales.js b/src/Umbraco.Web.UI.Client/lib/moment/moment-with-locales.js deleted file mode 100644 index 23d06ef355..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/moment/moment-with-locales.js +++ /dev/null @@ -1,9156 +0,0 @@ -//! moment.js -//! version : 2.8.3 -//! authors : Tim Wood, Iskren Chernev, Moment.js contributors -//! license : MIT -//! momentjs.com - -(function (undefined) { - /************************************ - Constants - ************************************/ - - var moment, - VERSION = '2.8.3', - // the global-scope this is NOT the global object in Node.js - globalScope = typeof global !== 'undefined' ? global : this, - oldGlobalMoment, - round = Math.round, - hasOwnProperty = Object.prototype.hasOwnProperty, - i, - - YEAR = 0, - MONTH = 1, - DATE = 2, - HOUR = 3, - MINUTE = 4, - SECOND = 5, - MILLISECOND = 6, - - // internal storage for locale config files - locales = {}, - - // extra moment internal properties (plugins register props here) - momentProperties = [], - - // check for nodeJS - hasModule = (typeof module !== 'undefined' && module.exports), - - // ASP.NET json date format regex - aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, - aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/, - - // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html - // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere - isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/, - - // format tokens - formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g, - localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g, - - // parsing token regexes - parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 - parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 - parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999 - parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999 - parseTokenDigits = /\d+/, // nonzero number of digits - parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic. - parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z - parseTokenT = /T/i, // T (ISO separator) - parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 - parseTokenOrdinal = /\d{1,2}/, - - //strict parsing regexes - parseTokenOneDigit = /\d/, // 0 - 9 - parseTokenTwoDigits = /\d\d/, // 00 - 99 - parseTokenThreeDigits = /\d{3}/, // 000 - 999 - parseTokenFourDigits = /\d{4}/, // 0000 - 9999 - parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999 - parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf - - // iso 8601 regex - // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) - isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/, - - isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', - - isoDates = [ - ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/], - ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/], - ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/], - ['GGGG-[W]WW', /\d{4}-W\d{2}/], - ['YYYY-DDD', /\d{4}-\d{3}/] - ], - - // iso time formats and regexes - isoTimes = [ - ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/], - ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], - ['HH:mm', /(T| )\d\d:\d\d/], - ['HH', /(T| )\d\d/] - ], - - // timezone chunker '+10:00' > ['10', '00'] or '-1530' > ['-15', '30'] - parseTimezoneChunker = /([\+\-]|\d\d)/gi, - - // getter and setter names - proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), - unitMillisecondFactors = { - 'Milliseconds' : 1, - 'Seconds' : 1e3, - 'Minutes' : 6e4, - 'Hours' : 36e5, - 'Days' : 864e5, - 'Months' : 2592e6, - 'Years' : 31536e6 - }, - - unitAliases = { - ms : 'millisecond', - s : 'second', - m : 'minute', - h : 'hour', - d : 'day', - D : 'date', - w : 'week', - W : 'isoWeek', - M : 'month', - Q : 'quarter', - y : 'year', - DDD : 'dayOfYear', - e : 'weekday', - E : 'isoWeekday', - gg: 'weekYear', - GG: 'isoWeekYear' - }, - - camelFunctions = { - dayofyear : 'dayOfYear', - isoweekday : 'isoWeekday', - isoweek : 'isoWeek', - weekyear : 'weekYear', - isoweekyear : 'isoWeekYear' - }, - - // format function strings - formatFunctions = {}, - - // default relative time thresholds - relativeTimeThresholds = { - s: 45, // seconds to minute - m: 45, // minutes to hour - h: 22, // hours to day - d: 26, // days to month - M: 11 // months to year - }, - - // tokens to ordinalize and pad - ordinalizeTokens = 'DDD w W M D d'.split(' '), - paddedTokens = 'M D H h m s w W'.split(' '), - - formatTokenFunctions = { - M : function () { - return this.month() + 1; - }, - MMM : function (format) { - return this.localeData().monthsShort(this, format); - }, - MMMM : function (format) { - return this.localeData().months(this, format); - }, - D : function () { - return this.date(); - }, - DDD : function () { - return this.dayOfYear(); - }, - d : function () { - return this.day(); - }, - dd : function (format) { - return this.localeData().weekdaysMin(this, format); - }, - ddd : function (format) { - return this.localeData().weekdaysShort(this, format); - }, - dddd : function (format) { - return this.localeData().weekdays(this, format); - }, - w : function () { - return this.week(); - }, - W : function () { - return this.isoWeek(); - }, - YY : function () { - return leftZeroFill(this.year() % 100, 2); - }, - YYYY : function () { - return leftZeroFill(this.year(), 4); - }, - YYYYY : function () { - return leftZeroFill(this.year(), 5); - }, - YYYYYY : function () { - var y = this.year(), sign = y >= 0 ? '+' : '-'; - return sign + leftZeroFill(Math.abs(y), 6); - }, - gg : function () { - return leftZeroFill(this.weekYear() % 100, 2); - }, - gggg : function () { - return leftZeroFill(this.weekYear(), 4); - }, - ggggg : function () { - return leftZeroFill(this.weekYear(), 5); - }, - GG : function () { - return leftZeroFill(this.isoWeekYear() % 100, 2); - }, - GGGG : function () { - return leftZeroFill(this.isoWeekYear(), 4); - }, - GGGGG : function () { - return leftZeroFill(this.isoWeekYear(), 5); - }, - e : function () { - return this.weekday(); - }, - E : function () { - return this.isoWeekday(); - }, - a : function () { - return this.localeData().meridiem(this.hours(), this.minutes(), true); - }, - A : function () { - return this.localeData().meridiem(this.hours(), this.minutes(), false); - }, - H : function () { - return this.hours(); - }, - h : function () { - return this.hours() % 12 || 12; - }, - m : function () { - return this.minutes(); - }, - s : function () { - return this.seconds(); - }, - S : function () { - return toInt(this.milliseconds() / 100); - }, - SS : function () { - return leftZeroFill(toInt(this.milliseconds() / 10), 2); - }, - SSS : function () { - return leftZeroFill(this.milliseconds(), 3); - }, - SSSS : function () { - return leftZeroFill(this.milliseconds(), 3); - }, - Z : function () { - var a = -this.zone(), - b = '+'; - if (a < 0) { - a = -a; - b = '-'; - } - return b + leftZeroFill(toInt(a / 60), 2) + ':' + leftZeroFill(toInt(a) % 60, 2); - }, - ZZ : function () { - var a = -this.zone(), - b = '+'; - if (a < 0) { - a = -a; - b = '-'; - } - return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2); - }, - z : function () { - return this.zoneAbbr(); - }, - zz : function () { - return this.zoneName(); - }, - X : function () { - return this.unix(); - }, - Q : function () { - return this.quarter(); - } - }, - - deprecations = {}, - - lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin']; - - // Pick the first defined of two or three arguments. dfl comes from - // default. - function dfl(a, b, c) { - switch (arguments.length) { - case 2: return a != null ? a : b; - case 3: return a != null ? a : b != null ? b : c; - default: throw new Error('Implement me'); - } - } - - function hasOwnProp(a, b) { - return hasOwnProperty.call(a, b); - } - - function defaultParsingFlags() { - // We need to deep clone this object, and es5 standard is not very - // helpful. - return { - empty : false, - unusedTokens : [], - unusedInput : [], - overflow : -2, - charsLeftOver : 0, - nullInput : false, - invalidMonth : null, - invalidFormat : false, - userInvalidated : false, - iso: false - }; - } - - function printMsg(msg) { - if (moment.suppressDeprecationWarnings === false && - typeof console !== 'undefined' && console.warn) { - console.warn('Deprecation warning: ' + msg); - } - } - - function deprecate(msg, fn) { - var firstTime = true; - return extend(function () { - if (firstTime) { - printMsg(msg); - firstTime = false; - } - return fn.apply(this, arguments); - }, fn); - } - - function deprecateSimple(name, msg) { - if (!deprecations[name]) { - printMsg(msg); - deprecations[name] = true; - } - } - - function padToken(func, count) { - return function (a) { - return leftZeroFill(func.call(this, a), count); - }; - } - function ordinalizeToken(func, period) { - return function (a) { - return this.localeData().ordinal(func.call(this, a), period); - }; - } - - while (ordinalizeTokens.length) { - i = ordinalizeTokens.pop(); - formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); - } - while (paddedTokens.length) { - i = paddedTokens.pop(); - formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); - } - formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); - - - /************************************ - Constructors - ************************************/ - - function Locale() { - } - - // Moment prototype object - function Moment(config, skipOverflow) { - if (skipOverflow !== false) { - checkOverflow(config); - } - copyConfig(this, config); - this._d = new Date(+config._d); - } - - // Duration Constructor - function Duration(duration) { - var normalizedInput = normalizeObjectUnits(duration), - years = normalizedInput.year || 0, - quarters = normalizedInput.quarter || 0, - months = normalizedInput.month || 0, - weeks = normalizedInput.week || 0, - days = normalizedInput.day || 0, - hours = normalizedInput.hour || 0, - minutes = normalizedInput.minute || 0, - seconds = normalizedInput.second || 0, - milliseconds = normalizedInput.millisecond || 0; - - // representation for dateAddRemove - this._milliseconds = +milliseconds + - seconds * 1e3 + // 1000 - minutes * 6e4 + // 1000 * 60 - hours * 36e5; // 1000 * 60 * 60 - // Because of dateAddRemove treats 24 hours as different from a - // day when working around DST, we need to store them separately - this._days = +days + - weeks * 7; - // It is impossible translate months into days without knowing - // which months you are are talking about, so we have to store - // it separately. - this._months = +months + - quarters * 3 + - years * 12; - - this._data = {}; - - this._locale = moment.localeData(); - - this._bubble(); - } - - /************************************ - Helpers - ************************************/ - - - function extend(a, b) { - for (var i in b) { - if (hasOwnProp(b, i)) { - a[i] = b[i]; - } - } - - if (hasOwnProp(b, 'toString')) { - a.toString = b.toString; - } - - if (hasOwnProp(b, 'valueOf')) { - a.valueOf = b.valueOf; - } - - return a; - } - - function copyConfig(to, from) { - var i, prop, val; - - if (typeof from._isAMomentObject !== 'undefined') { - to._isAMomentObject = from._isAMomentObject; - } - if (typeof from._i !== 'undefined') { - to._i = from._i; - } - if (typeof from._f !== 'undefined') { - to._f = from._f; - } - if (typeof from._l !== 'undefined') { - to._l = from._l; - } - if (typeof from._strict !== 'undefined') { - to._strict = from._strict; - } - if (typeof from._tzm !== 'undefined') { - to._tzm = from._tzm; - } - if (typeof from._isUTC !== 'undefined') { - to._isUTC = from._isUTC; - } - if (typeof from._offset !== 'undefined') { - to._offset = from._offset; - } - if (typeof from._pf !== 'undefined') { - to._pf = from._pf; - } - if (typeof from._locale !== 'undefined') { - to._locale = from._locale; - } - - if (momentProperties.length > 0) { - for (i in momentProperties) { - prop = momentProperties[i]; - val = from[prop]; - if (typeof val !== 'undefined') { - to[prop] = val; - } - } - } - - return to; - } - - function absRound(number) { - if (number < 0) { - return Math.ceil(number); - } else { - return Math.floor(number); - } - } - - // left zero fill a number - // see http://jsperf.com/left-zero-filling for performance comparison - function leftZeroFill(number, targetLength, forceSign) { - var output = '' + Math.abs(number), - sign = number >= 0; - - while (output.length < targetLength) { - output = '0' + output; - } - return (sign ? (forceSign ? '+' : '') : '-') + output; - } - - function positiveMomentsDifference(base, other) { - var res = {milliseconds: 0, months: 0}; - - res.months = other.month() - base.month() + - (other.year() - base.year()) * 12; - if (base.clone().add(res.months, 'M').isAfter(other)) { - --res.months; - } - - res.milliseconds = +other - +(base.clone().add(res.months, 'M')); - - return res; - } - - function momentsDifference(base, other) { - var res; - other = makeAs(other, base); - if (base.isBefore(other)) { - res = positiveMomentsDifference(base, other); - } else { - res = positiveMomentsDifference(other, base); - res.milliseconds = -res.milliseconds; - res.months = -res.months; - } - - return res; - } - - // TODO: remove 'name' arg after deprecation is removed - function createAdder(direction, name) { - return function (val, period) { - var dur, tmp; - //invert the arguments, but complain about it - if (period !== null && !isNaN(+period)) { - deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).'); - tmp = val; val = period; period = tmp; - } - - val = typeof val === 'string' ? +val : val; - dur = moment.duration(val, period); - addOrSubtractDurationFromMoment(this, dur, direction); - return this; - }; - } - - function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) { - var milliseconds = duration._milliseconds, - days = duration._days, - months = duration._months; - updateOffset = updateOffset == null ? true : updateOffset; - - if (milliseconds) { - mom._d.setTime(+mom._d + milliseconds * isAdding); - } - if (days) { - rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding); - } - if (months) { - rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding); - } - if (updateOffset) { - moment.updateOffset(mom, days || months); - } - } - - // check if is an array - function isArray(input) { - return Object.prototype.toString.call(input) === '[object Array]'; - } - - function isDate(input) { - return Object.prototype.toString.call(input) === '[object Date]' || - input instanceof Date; - } - - // compare two arrays, return the number of differences - function compareArrays(array1, array2, dontConvert) { - var len = Math.min(array1.length, array2.length), - lengthDiff = Math.abs(array1.length - array2.length), - diffs = 0, - i; - for (i = 0; i < len; i++) { - if ((dontConvert && array1[i] !== array2[i]) || - (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { - diffs++; - } - } - return diffs + lengthDiff; - } - - function normalizeUnits(units) { - if (units) { - var lowered = units.toLowerCase().replace(/(.)s$/, '$1'); - units = unitAliases[units] || camelFunctions[lowered] || lowered; - } - return units; - } - - function normalizeObjectUnits(inputObject) { - var normalizedInput = {}, - normalizedProp, - prop; - - for (prop in inputObject) { - if (hasOwnProp(inputObject, prop)) { - normalizedProp = normalizeUnits(prop); - if (normalizedProp) { - normalizedInput[normalizedProp] = inputObject[prop]; - } - } - } - - return normalizedInput; - } - - function makeList(field) { - var count, setter; - - if (field.indexOf('week') === 0) { - count = 7; - setter = 'day'; - } - else if (field.indexOf('month') === 0) { - count = 12; - setter = 'month'; - } - else { - return; - } - - moment[field] = function (format, index) { - var i, getter, - method = moment._locale[field], - results = []; - - if (typeof format === 'number') { - index = format; - format = undefined; - } - - getter = function (i) { - var m = moment().utc().set(setter, i); - return method.call(moment._locale, m, format || ''); - }; - - if (index != null) { - return getter(index); - } - else { - for (i = 0; i < count; i++) { - results.push(getter(i)); - } - return results; - } - }; - } - - function toInt(argumentForCoercion) { - var coercedNumber = +argumentForCoercion, - value = 0; - - if (coercedNumber !== 0 && isFinite(coercedNumber)) { - if (coercedNumber >= 0) { - value = Math.floor(coercedNumber); - } else { - value = Math.ceil(coercedNumber); - } - } - - return value; - } - - function daysInMonth(year, month) { - return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); - } - - function weeksInYear(year, dow, doy) { - return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week; - } - - function daysInYear(year) { - return isLeapYear(year) ? 366 : 365; - } - - function isLeapYear(year) { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; - } - - function checkOverflow(m) { - var overflow; - if (m._a && m._pf.overflow === -2) { - overflow = - m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH : - m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE : - m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR : - m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE : - m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND : - m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND : - -1; - - if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { - overflow = DATE; - } - - m._pf.overflow = overflow; - } - } - - function isValid(m) { - if (m._isValid == null) { - m._isValid = !isNaN(m._d.getTime()) && - m._pf.overflow < 0 && - !m._pf.empty && - !m._pf.invalidMonth && - !m._pf.nullInput && - !m._pf.invalidFormat && - !m._pf.userInvalidated; - - if (m._strict) { - m._isValid = m._isValid && - m._pf.charsLeftOver === 0 && - m._pf.unusedTokens.length === 0; - } - } - return m._isValid; - } - - function normalizeLocale(key) { - return key ? key.toLowerCase().replace('_', '-') : key; - } - - // pick the locale from the array - // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each - // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root - function chooseLocale(names) { - var i = 0, j, next, locale, split; - - while (i < names.length) { - split = normalizeLocale(names[i]).split('-'); - j = split.length; - next = normalizeLocale(names[i + 1]); - next = next ? next.split('-') : null; - while (j > 0) { - locale = loadLocale(split.slice(0, j).join('-')); - if (locale) { - return locale; - } - if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { - //the next array item is better than a shallower substring of this one - break; - } - j--; - } - i++; - } - return null; - } - - function loadLocale(name) { - var oldLocale = null; - if (!locales[name] && hasModule) { - try { - oldLocale = moment.locale(); - require('./locale/' + name); - // because defineLocale currently also sets the global locale, we want to undo that for lazy loaded locales - moment.locale(oldLocale); - } catch (e) { } - } - return locales[name]; - } - - // Return a moment from input, that is local/utc/zone equivalent to model. - function makeAs(input, model) { - return model._isUTC ? moment(input).zone(model._offset || 0) : - moment(input).local(); - } - - /************************************ - Locale - ************************************/ - - - extend(Locale.prototype, { - - set : function (config) { - var prop, i; - for (i in config) { - prop = config[i]; - if (typeof prop === 'function') { - this[i] = prop; - } else { - this['_' + i] = prop; - } - } - }, - - _months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), - months : function (m) { - return this._months[m.month()]; - }, - - _monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), - monthsShort : function (m) { - return this._monthsShort[m.month()]; - }, - - monthsParse : function (monthName) { - var i, mom, regex; - - if (!this._monthsParse) { - this._monthsParse = []; - } - - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - if (!this._monthsParse[i]) { - mom = moment.utc([2000, i]); - regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); - this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (this._monthsParse[i].test(monthName)) { - return i; - } - } - }, - - _weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), - weekdays : function (m) { - return this._weekdays[m.day()]; - }, - - _weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), - weekdaysShort : function (m) { - return this._weekdaysShort[m.day()]; - }, - - _weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), - weekdaysMin : function (m) { - return this._weekdaysMin[m.day()]; - }, - - weekdaysParse : function (weekdayName) { - var i, mom, regex; - - if (!this._weekdaysParse) { - this._weekdaysParse = []; - } - - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already - if (!this._weekdaysParse[i]) { - mom = moment([2000, 1]).day(i); - regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); - this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (this._weekdaysParse[i].test(weekdayName)) { - return i; - } - } - }, - - _longDateFormat : { - LT : 'h:mm A', - L : 'MM/DD/YYYY', - LL : 'MMMM D, YYYY', - LLL : 'MMMM D, YYYY LT', - LLLL : 'dddd, MMMM D, YYYY LT' - }, - longDateFormat : function (key) { - var output = this._longDateFormat[key]; - if (!output && this._longDateFormat[key.toUpperCase()]) { - output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { - return val.slice(1); - }); - this._longDateFormat[key] = output; - } - return output; - }, - - isPM : function (input) { - // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays - // Using charAt should be more compatible. - return ((input + '').toLowerCase().charAt(0) === 'p'); - }, - - _meridiemParse : /[ap]\.?m?\.?/i, - meridiem : function (hours, minutes, isLower) { - if (hours > 11) { - return isLower ? 'pm' : 'PM'; - } else { - return isLower ? 'am' : 'AM'; - } - }, - - _calendar : { - sameDay : '[Today at] LT', - nextDay : '[Tomorrow at] LT', - nextWeek : 'dddd [at] LT', - lastDay : '[Yesterday at] LT', - lastWeek : '[Last] dddd [at] LT', - sameElse : 'L' - }, - calendar : function (key, mom) { - var output = this._calendar[key]; - return typeof output === 'function' ? output.apply(mom) : output; - }, - - _relativeTime : { - future : 'in %s', - past : '%s ago', - s : 'a few seconds', - m : 'a minute', - mm : '%d minutes', - h : 'an hour', - hh : '%d hours', - d : 'a day', - dd : '%d days', - M : 'a month', - MM : '%d months', - y : 'a year', - yy : '%d years' - }, - - relativeTime : function (number, withoutSuffix, string, isFuture) { - var output = this._relativeTime[string]; - return (typeof output === 'function') ? - output(number, withoutSuffix, string, isFuture) : - output.replace(/%d/i, number); - }, - - pastFuture : function (diff, output) { - var format = this._relativeTime[diff > 0 ? 'future' : 'past']; - return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); - }, - - ordinal : function (number) { - return this._ordinal.replace('%d', number); - }, - _ordinal : '%d', - - preparse : function (string) { - return string; - }, - - postformat : function (string) { - return string; - }, - - week : function (mom) { - return weekOfYear(mom, this._week.dow, this._week.doy).week; - }, - - _week : { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 1st is the first week of the year. - }, - - _invalidDate: 'Invalid date', - invalidDate: function () { - return this._invalidDate; - } - }); - - /************************************ - Formatting - ************************************/ - - - function removeFormattingTokens(input) { - if (input.match(/\[[\s\S]/)) { - return input.replace(/^\[|\]$/g, ''); - } - return input.replace(/\\/g, ''); - } - - function makeFormatFunction(format) { - var array = format.match(formattingTokens), i, length; - - for (i = 0, length = array.length; i < length; i++) { - if (formatTokenFunctions[array[i]]) { - array[i] = formatTokenFunctions[array[i]]; - } else { - array[i] = removeFormattingTokens(array[i]); - } - } - - return function (mom) { - var output = ''; - for (i = 0; i < length; i++) { - output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; - } - return output; - }; - } - - // format date using native date object - function formatMoment(m, format) { - if (!m.isValid()) { - return m.localeData().invalidDate(); - } - - format = expandFormat(format, m.localeData()); - - if (!formatFunctions[format]) { - formatFunctions[format] = makeFormatFunction(format); - } - - return formatFunctions[format](m); - } - - function expandFormat(format, locale) { - var i = 5; - - function replaceLongDateFormatTokens(input) { - return locale.longDateFormat(input) || input; - } - - localFormattingTokens.lastIndex = 0; - while (i >= 0 && localFormattingTokens.test(format)) { - format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); - localFormattingTokens.lastIndex = 0; - i -= 1; - } - - return format; - } - - - /************************************ - Parsing - ************************************/ - - - // get the regex to find the next token - function getParseRegexForToken(token, config) { - var a, strict = config._strict; - switch (token) { - case 'Q': - return parseTokenOneDigit; - case 'DDDD': - return parseTokenThreeDigits; - case 'YYYY': - case 'GGGG': - case 'gggg': - return strict ? parseTokenFourDigits : parseTokenOneToFourDigits; - case 'Y': - case 'G': - case 'g': - return parseTokenSignedNumber; - case 'YYYYYY': - case 'YYYYY': - case 'GGGGG': - case 'ggggg': - return strict ? parseTokenSixDigits : parseTokenOneToSixDigits; - case 'S': - if (strict) { - return parseTokenOneDigit; - } - /* falls through */ - case 'SS': - if (strict) { - return parseTokenTwoDigits; - } - /* falls through */ - case 'SSS': - if (strict) { - return parseTokenThreeDigits; - } - /* falls through */ - case 'DDD': - return parseTokenOneToThreeDigits; - case 'MMM': - case 'MMMM': - case 'dd': - case 'ddd': - case 'dddd': - return parseTokenWord; - case 'a': - case 'A': - return config._locale._meridiemParse; - case 'X': - return parseTokenTimestampMs; - case 'Z': - case 'ZZ': - return parseTokenTimezone; - case 'T': - return parseTokenT; - case 'SSSS': - return parseTokenDigits; - case 'MM': - case 'DD': - case 'YY': - case 'GG': - case 'gg': - case 'HH': - case 'hh': - case 'mm': - case 'ss': - case 'ww': - case 'WW': - return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits; - case 'M': - case 'D': - case 'd': - case 'H': - case 'h': - case 'm': - case 's': - case 'w': - case 'W': - case 'e': - case 'E': - return parseTokenOneOrTwoDigits; - case 'Do': - return parseTokenOrdinal; - default : - a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), 'i')); - return a; - } - } - - function timezoneMinutesFromString(string) { - string = string || ''; - var possibleTzMatches = (string.match(parseTokenTimezone) || []), - tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [], - parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0], - minutes = +(parts[1] * 60) + toInt(parts[2]); - - return parts[0] === '+' ? -minutes : minutes; - } - - // function to convert string input to date - function addTimeToArrayFromToken(token, input, config) { - var a, datePartArray = config._a; - - switch (token) { - // QUARTER - case 'Q': - if (input != null) { - datePartArray[MONTH] = (toInt(input) - 1) * 3; - } - break; - // MONTH - case 'M' : // fall through to MM - case 'MM' : - if (input != null) { - datePartArray[MONTH] = toInt(input) - 1; - } - break; - case 'MMM' : // fall through to MMMM - case 'MMMM' : - a = config._locale.monthsParse(input); - // if we didn't find a month name, mark the date as invalid. - if (a != null) { - datePartArray[MONTH] = a; - } else { - config._pf.invalidMonth = input; - } - break; - // DAY OF MONTH - case 'D' : // fall through to DD - case 'DD' : - if (input != null) { - datePartArray[DATE] = toInt(input); - } - break; - case 'Do' : - if (input != null) { - datePartArray[DATE] = toInt(parseInt(input, 10)); - } - break; - // DAY OF YEAR - case 'DDD' : // fall through to DDDD - case 'DDDD' : - if (input != null) { - config._dayOfYear = toInt(input); - } - - break; - // YEAR - case 'YY' : - datePartArray[YEAR] = moment.parseTwoDigitYear(input); - break; - case 'YYYY' : - case 'YYYYY' : - case 'YYYYYY' : - datePartArray[YEAR] = toInt(input); - break; - // AM / PM - case 'a' : // fall through to A - case 'A' : - config._isPm = config._locale.isPM(input); - break; - // 24 HOUR - case 'H' : // fall through to hh - case 'HH' : // fall through to hh - case 'h' : // fall through to hh - case 'hh' : - datePartArray[HOUR] = toInt(input); - break; - // MINUTE - case 'm' : // fall through to mm - case 'mm' : - datePartArray[MINUTE] = toInt(input); - break; - // SECOND - case 's' : // fall through to ss - case 'ss' : - datePartArray[SECOND] = toInt(input); - break; - // MILLISECOND - case 'S' : - case 'SS' : - case 'SSS' : - case 'SSSS' : - datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000); - break; - // UNIX TIMESTAMP WITH MS - case 'X': - config._d = new Date(parseFloat(input) * 1000); - break; - // TIMEZONE - case 'Z' : // fall through to ZZ - case 'ZZ' : - config._useUTC = true; - config._tzm = timezoneMinutesFromString(input); - break; - // WEEKDAY - human - case 'dd': - case 'ddd': - case 'dddd': - a = config._locale.weekdaysParse(input); - // if we didn't get a weekday name, mark the date as invalid - if (a != null) { - config._w = config._w || {}; - config._w['d'] = a; - } else { - config._pf.invalidWeekday = input; - } - break; - // WEEK, WEEK DAY - numeric - case 'w': - case 'ww': - case 'W': - case 'WW': - case 'd': - case 'e': - case 'E': - token = token.substr(0, 1); - /* falls through */ - case 'gggg': - case 'GGGG': - case 'GGGGG': - token = token.substr(0, 2); - if (input) { - config._w = config._w || {}; - config._w[token] = toInt(input); - } - break; - case 'gg': - case 'GG': - config._w = config._w || {}; - config._w[token] = moment.parseTwoDigitYear(input); - } - } - - function dayOfYearFromWeekInfo(config) { - var w, weekYear, week, weekday, dow, doy, temp; - - w = config._w; - if (w.GG != null || w.W != null || w.E != null) { - dow = 1; - doy = 4; - - // TODO: We need to take the current isoWeekYear, but that depends on - // how we interpret now (local, utc, fixed offset). So create - // a now version of current config (take local/utc/offset flags, and - // create now). - weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year); - week = dfl(w.W, 1); - weekday = dfl(w.E, 1); - } else { - dow = config._locale._week.dow; - doy = config._locale._week.doy; - - weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year); - week = dfl(w.w, 1); - - if (w.d != null) { - // weekday -- low day numbers are considered next week - weekday = w.d; - if (weekday < dow) { - ++week; - } - } else if (w.e != null) { - // local weekday -- counting starts from begining of week - weekday = w.e + dow; - } else { - // default to begining of week - weekday = dow; - } - } - temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow); - - config._a[YEAR] = temp.year; - config._dayOfYear = temp.dayOfYear; - } - - // convert an array to a date. - // the array should mirror the parameters below - // note: all values past the year are optional and will default to the lowest possible value. - // [year, month, day , hour, minute, second, millisecond] - function dateFromConfig(config) { - var i, date, input = [], currentDate, yearToUse; - - if (config._d) { - return; - } - - currentDate = currentDateArray(config); - - //compute day of the year from weeks and weekdays - if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { - dayOfYearFromWeekInfo(config); - } - - //if the day of the year is set, figure out what it is - if (config._dayOfYear) { - yearToUse = dfl(config._a[YEAR], currentDate[YEAR]); - - if (config._dayOfYear > daysInYear(yearToUse)) { - config._pf._overflowDayOfYear = true; - } - - date = makeUTCDate(yearToUse, 0, config._dayOfYear); - config._a[MONTH] = date.getUTCMonth(); - config._a[DATE] = date.getUTCDate(); - } - - // Default to current date. - // * if no year, month, day of month are given, default to today - // * if day of month is given, default month and year - // * if month is given, default only year - // * if year is given, don't default anything - for (i = 0; i < 3 && config._a[i] == null; ++i) { - config._a[i] = input[i] = currentDate[i]; - } - - // Zero out whatever was not defaulted, including time - for (; i < 7; i++) { - config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; - } - - config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); - // Apply timezone offset from input. The actual zone can be changed - // with parseZone. - if (config._tzm != null) { - config._d.setUTCMinutes(config._d.getUTCMinutes() + config._tzm); - } - } - - function dateFromObject(config) { - var normalizedInput; - - if (config._d) { - return; - } - - normalizedInput = normalizeObjectUnits(config._i); - config._a = [ - normalizedInput.year, - normalizedInput.month, - normalizedInput.day, - normalizedInput.hour, - normalizedInput.minute, - normalizedInput.second, - normalizedInput.millisecond - ]; - - dateFromConfig(config); - } - - function currentDateArray(config) { - var now = new Date(); - if (config._useUTC) { - return [ - now.getUTCFullYear(), - now.getUTCMonth(), - now.getUTCDate() - ]; - } else { - return [now.getFullYear(), now.getMonth(), now.getDate()]; - } - } - - // date from string and format string - function makeDateFromStringAndFormat(config) { - if (config._f === moment.ISO_8601) { - parseISO(config); - return; - } - - config._a = []; - config._pf.empty = true; - - // This array is used to make a Date, either with `new Date` or `Date.UTC` - var string = '' + config._i, - i, parsedInput, tokens, token, skipped, - stringLength = string.length, - totalParsedInputLength = 0; - - tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; - - for (i = 0; i < tokens.length; i++) { - token = tokens[i]; - parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; - if (parsedInput) { - skipped = string.substr(0, string.indexOf(parsedInput)); - if (skipped.length > 0) { - config._pf.unusedInput.push(skipped); - } - string = string.slice(string.indexOf(parsedInput) + parsedInput.length); - totalParsedInputLength += parsedInput.length; - } - // don't parse if it's not a known token - if (formatTokenFunctions[token]) { - if (parsedInput) { - config._pf.empty = false; - } - else { - config._pf.unusedTokens.push(token); - } - addTimeToArrayFromToken(token, parsedInput, config); - } - else if (config._strict && !parsedInput) { - config._pf.unusedTokens.push(token); - } - } - - // add remaining unparsed input length to the string - config._pf.charsLeftOver = stringLength - totalParsedInputLength; - if (string.length > 0) { - config._pf.unusedInput.push(string); - } - - // handle am pm - if (config._isPm && config._a[HOUR] < 12) { - config._a[HOUR] += 12; - } - // if is 12 am, change hours to 0 - if (config._isPm === false && config._a[HOUR] === 12) { - config._a[HOUR] = 0; - } - - dateFromConfig(config); - checkOverflow(config); - } - - function unescapeFormat(s) { - return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { - return p1 || p2 || p3 || p4; - }); - } - - // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript - function regexpEscape(s) { - return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); - } - - // date from string and array of format strings - function makeDateFromStringAndArray(config) { - var tempConfig, - bestMoment, - - scoreToBeat, - i, - currentScore; - - if (config._f.length === 0) { - config._pf.invalidFormat = true; - config._d = new Date(NaN); - return; - } - - for (i = 0; i < config._f.length; i++) { - currentScore = 0; - tempConfig = copyConfig({}, config); - if (config._useUTC != null) { - tempConfig._useUTC = config._useUTC; - } - tempConfig._pf = defaultParsingFlags(); - tempConfig._f = config._f[i]; - makeDateFromStringAndFormat(tempConfig); - - if (!isValid(tempConfig)) { - continue; - } - - // if there is any input that was not parsed add a penalty for that format - currentScore += tempConfig._pf.charsLeftOver; - - //or tokens - currentScore += tempConfig._pf.unusedTokens.length * 10; - - tempConfig._pf.score = currentScore; - - if (scoreToBeat == null || currentScore < scoreToBeat) { - scoreToBeat = currentScore; - bestMoment = tempConfig; - } - } - - extend(config, bestMoment || tempConfig); - } - - // date from iso format - function parseISO(config) { - var i, l, - string = config._i, - match = isoRegex.exec(string); - - if (match) { - config._pf.iso = true; - for (i = 0, l = isoDates.length; i < l; i++) { - if (isoDates[i][1].exec(string)) { - // match[5] should be 'T' or undefined - config._f = isoDates[i][0] + (match[6] || ' '); - break; - } - } - for (i = 0, l = isoTimes.length; i < l; i++) { - if (isoTimes[i][1].exec(string)) { - config._f += isoTimes[i][0]; - break; - } - } - if (string.match(parseTokenTimezone)) { - config._f += 'Z'; - } - makeDateFromStringAndFormat(config); - } else { - config._isValid = false; - } - } - - // date from iso format or fallback - function makeDateFromString(config) { - parseISO(config); - if (config._isValid === false) { - delete config._isValid; - moment.createFromInputFallback(config); - } - } - - function map(arr, fn) { - var res = [], i; - for (i = 0; i < arr.length; ++i) { - res.push(fn(arr[i], i)); - } - return res; - } - - function makeDateFromInput(config) { - var input = config._i, matched; - if (input === undefined) { - config._d = new Date(); - } else if (isDate(input)) { - config._d = new Date(+input); - } else if ((matched = aspNetJsonRegex.exec(input)) !== null) { - config._d = new Date(+matched[1]); - } else if (typeof input === 'string') { - makeDateFromString(config); - } else if (isArray(input)) { - config._a = map(input.slice(0), function (obj) { - return parseInt(obj, 10); - }); - dateFromConfig(config); - } else if (typeof(input) === 'object') { - dateFromObject(config); - } else if (typeof(input) === 'number') { - // from milliseconds - config._d = new Date(input); - } else { - moment.createFromInputFallback(config); - } - } - - function makeDate(y, m, d, h, M, s, ms) { - //can't just apply() to create a date: - //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply - var date = new Date(y, m, d, h, M, s, ms); - - //the date constructor doesn't accept years < 1970 - if (y < 1970) { - date.setFullYear(y); - } - return date; - } - - function makeUTCDate(y) { - var date = new Date(Date.UTC.apply(null, arguments)); - if (y < 1970) { - date.setUTCFullYear(y); - } - return date; - } - - function parseWeekday(input, locale) { - if (typeof input === 'string') { - if (!isNaN(input)) { - input = parseInt(input, 10); - } - else { - input = locale.weekdaysParse(input); - if (typeof input !== 'number') { - return null; - } - } - } - return input; - } - - /************************************ - Relative Time - ************************************/ - - - // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { - return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); - } - - function relativeTime(posNegDuration, withoutSuffix, locale) { - var duration = moment.duration(posNegDuration).abs(), - seconds = round(duration.as('s')), - minutes = round(duration.as('m')), - hours = round(duration.as('h')), - days = round(duration.as('d')), - months = round(duration.as('M')), - years = round(duration.as('y')), - - args = seconds < relativeTimeThresholds.s && ['s', seconds] || - minutes === 1 && ['m'] || - minutes < relativeTimeThresholds.m && ['mm', minutes] || - hours === 1 && ['h'] || - hours < relativeTimeThresholds.h && ['hh', hours] || - days === 1 && ['d'] || - days < relativeTimeThresholds.d && ['dd', days] || - months === 1 && ['M'] || - months < relativeTimeThresholds.M && ['MM', months] || - years === 1 && ['y'] || ['yy', years]; - - args[2] = withoutSuffix; - args[3] = +posNegDuration > 0; - args[4] = locale; - return substituteTimeAgo.apply({}, args); - } - - - /************************************ - Week of Year - ************************************/ - - - // firstDayOfWeek 0 = sun, 6 = sat - // the day of the week that starts the week - // (usually sunday or monday) - // firstDayOfWeekOfYear 0 = sun, 6 = sat - // the first week is the week that contains the first - // of this day of the week - // (eg. ISO weeks use thursday (4)) - function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { - var end = firstDayOfWeekOfYear - firstDayOfWeek, - daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), - adjustedMoment; - - - if (daysToDayOfWeek > end) { - daysToDayOfWeek -= 7; - } - - if (daysToDayOfWeek < end - 7) { - daysToDayOfWeek += 7; - } - - adjustedMoment = moment(mom).add(daysToDayOfWeek, 'd'); - return { - week: Math.ceil(adjustedMoment.dayOfYear() / 7), - year: adjustedMoment.year() - }; - } - - //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday - function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { - var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear; - - d = d === 0 ? 7 : d; - weekday = weekday != null ? weekday : firstDayOfWeek; - daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); - dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; - - return { - year: dayOfYear > 0 ? year : year - 1, - dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear - }; - } - - /************************************ - Top Level Functions - ************************************/ - - function makeMoment(config) { - var input = config._i, - format = config._f; - - config._locale = config._locale || moment.localeData(config._l); - - if (input === null || (format === undefined && input === '')) { - return moment.invalid({nullInput: true}); - } - - if (typeof input === 'string') { - config._i = input = config._locale.preparse(input); - } - - if (moment.isMoment(input)) { - return new Moment(input, true); - } else if (format) { - if (isArray(format)) { - makeDateFromStringAndArray(config); - } else { - makeDateFromStringAndFormat(config); - } - } else { - makeDateFromInput(config); - } - - return new Moment(config); - } - - moment = function (input, format, locale, strict) { - var c; - - if (typeof(locale) === 'boolean') { - strict = locale; - locale = undefined; - } - // object construction must be done this way. - // https://github.com/moment/moment/issues/1423 - c = {}; - c._isAMomentObject = true; - c._i = input; - c._f = format; - c._l = locale; - c._strict = strict; - c._isUTC = false; - c._pf = defaultParsingFlags(); - - return makeMoment(c); - }; - - moment.suppressDeprecationWarnings = false; - - moment.createFromInputFallback = deprecate( - 'moment construction falls back to js Date. This is ' + - 'discouraged and will be removed in upcoming major ' + - 'release. Please refer to ' + - 'https://github.com/moment/moment/issues/1407 for more info.', - function (config) { - config._d = new Date(config._i); - } - ); - - // Pick a moment m from moments so that m[fn](other) is true for all - // other. This relies on the function fn to be transitive. - // - // moments should either be an array of moment objects or an array, whose - // first element is an array of moment objects. - function pickBy(fn, moments) { - var res, i; - if (moments.length === 1 && isArray(moments[0])) { - moments = moments[0]; - } - if (!moments.length) { - return moment(); - } - res = moments[0]; - for (i = 1; i < moments.length; ++i) { - if (moments[i][fn](res)) { - res = moments[i]; - } - } - return res; - } - - moment.min = function () { - var args = [].slice.call(arguments, 0); - - return pickBy('isBefore', args); - }; - - moment.max = function () { - var args = [].slice.call(arguments, 0); - - return pickBy('isAfter', args); - }; - - // creating with utc - moment.utc = function (input, format, locale, strict) { - var c; - - if (typeof(locale) === 'boolean') { - strict = locale; - locale = undefined; - } - // object construction must be done this way. - // https://github.com/moment/moment/issues/1423 - c = {}; - c._isAMomentObject = true; - c._useUTC = true; - c._isUTC = true; - c._l = locale; - c._i = input; - c._f = format; - c._strict = strict; - c._pf = defaultParsingFlags(); - - return makeMoment(c).utc(); - }; - - // creating with unix timestamp (in seconds) - moment.unix = function (input) { - return moment(input * 1000); - }; - - // duration - moment.duration = function (input, key) { - var duration = input, - // matching against regexp is expensive, do it on demand - match = null, - sign, - ret, - parseIso, - diffRes; - - if (moment.isDuration(input)) { - duration = { - ms: input._milliseconds, - d: input._days, - M: input._months - }; - } else if (typeof input === 'number') { - duration = {}; - if (key) { - duration[key] = input; - } else { - duration.milliseconds = input; - } - } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) { - sign = (match[1] === '-') ? -1 : 1; - duration = { - y: 0, - d: toInt(match[DATE]) * sign, - h: toInt(match[HOUR]) * sign, - m: toInt(match[MINUTE]) * sign, - s: toInt(match[SECOND]) * sign, - ms: toInt(match[MILLISECOND]) * sign - }; - } else if (!!(match = isoDurationRegex.exec(input))) { - sign = (match[1] === '-') ? -1 : 1; - parseIso = function (inp) { - // We'd normally use ~~inp for this, but unfortunately it also - // converts floats to ints. - // inp may be undefined, so careful calling replace on it. - var res = inp && parseFloat(inp.replace(',', '.')); - // apply sign while we're at it - return (isNaN(res) ? 0 : res) * sign; - }; - duration = { - y: parseIso(match[2]), - M: parseIso(match[3]), - d: parseIso(match[4]), - h: parseIso(match[5]), - m: parseIso(match[6]), - s: parseIso(match[7]), - w: parseIso(match[8]) - }; - } else if (typeof duration === 'object' && - ('from' in duration || 'to' in duration)) { - diffRes = momentsDifference(moment(duration.from), moment(duration.to)); - - duration = {}; - duration.ms = diffRes.milliseconds; - duration.M = diffRes.months; - } - - ret = new Duration(duration); - - if (moment.isDuration(input) && hasOwnProp(input, '_locale')) { - ret._locale = input._locale; - } - - return ret; - }; - - // version number - moment.version = VERSION; - - // default format - moment.defaultFormat = isoFormat; - - // constant that refers to the ISO standard - moment.ISO_8601 = function () {}; - - // Plugins that add properties should also add the key here (null value), - // so we can properly clone ourselves. - moment.momentProperties = momentProperties; - - // This function will be called whenever a moment is mutated. - // It is intended to keep the offset in sync with the timezone. - moment.updateOffset = function () {}; - - // This function allows you to set a threshold for relative time strings - moment.relativeTimeThreshold = function (threshold, limit) { - if (relativeTimeThresholds[threshold] === undefined) { - return false; - } - if (limit === undefined) { - return relativeTimeThresholds[threshold]; - } - relativeTimeThresholds[threshold] = limit; - return true; - }; - - moment.lang = deprecate( - 'moment.lang is deprecated. Use moment.locale instead.', - function (key, value) { - return moment.locale(key, value); - } - ); - - // This function will load locale and then set the global locale. If - // no arguments are passed in, it will simply return the current global - // locale key. - moment.locale = function (key, values) { - var data; - if (key) { - if (typeof(values) !== 'undefined') { - data = moment.defineLocale(key, values); - } - else { - data = moment.localeData(key); - } - - if (data) { - moment.duration._locale = moment._locale = data; - } - } - - return moment._locale._abbr; - }; - - moment.defineLocale = function (name, values) { - if (values !== null) { - values.abbr = name; - if (!locales[name]) { - locales[name] = new Locale(); - } - locales[name].set(values); - - // backwards compat for now: also set the locale - moment.locale(name); - - return locales[name]; - } else { - // useful for testing - delete locales[name]; - return null; - } - }; - - moment.langData = deprecate( - 'moment.langData is deprecated. Use moment.localeData instead.', - function (key) { - return moment.localeData(key); - } - ); - - // returns locale data - moment.localeData = function (key) { - var locale; - - if (key && key._locale && key._locale._abbr) { - key = key._locale._abbr; - } - - if (!key) { - return moment._locale; - } - - if (!isArray(key)) { - //short-circuit everything else - locale = loadLocale(key); - if (locale) { - return locale; - } - key = [key]; - } - - return chooseLocale(key); - }; - - // compare moment object - moment.isMoment = function (obj) { - return obj instanceof Moment || - (obj != null && hasOwnProp(obj, '_isAMomentObject')); - }; - - // for typechecking Duration objects - moment.isDuration = function (obj) { - return obj instanceof Duration; - }; - - for (i = lists.length - 1; i >= 0; --i) { - makeList(lists[i]); - } - - moment.normalizeUnits = function (units) { - return normalizeUnits(units); - }; - - moment.invalid = function (flags) { - var m = moment.utc(NaN); - if (flags != null) { - extend(m._pf, flags); - } - else { - m._pf.userInvalidated = true; - } - - return m; - }; - - moment.parseZone = function () { - return moment.apply(null, arguments).parseZone(); - }; - - moment.parseTwoDigitYear = function (input) { - return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); - }; - - /************************************ - Moment Prototype - ************************************/ - - - extend(moment.fn = Moment.prototype, { - - clone : function () { - return moment(this); - }, - - valueOf : function () { - return +this._d + ((this._offset || 0) * 60000); - }, - - unix : function () { - return Math.floor(+this / 1000); - }, - - toString : function () { - return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); - }, - - toDate : function () { - return this._offset ? new Date(+this) : this._d; - }, - - toISOString : function () { - var m = moment(this).utc(); - if (0 < m.year() && m.year() <= 9999) { - return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } else { - return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } - }, - - toArray : function () { - var m = this; - return [ - m.year(), - m.month(), - m.date(), - m.hours(), - m.minutes(), - m.seconds(), - m.milliseconds() - ]; - }, - - isValid : function () { - return isValid(this); - }, - - isDSTShifted : function () { - if (this._a) { - return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0; - } - - return false; - }, - - parsingFlags : function () { - return extend({}, this._pf); - }, - - invalidAt: function () { - return this._pf.overflow; - }, - - utc : function (keepLocalTime) { - return this.zone(0, keepLocalTime); - }, - - local : function (keepLocalTime) { - if (this._isUTC) { - this.zone(0, keepLocalTime); - this._isUTC = false; - - if (keepLocalTime) { - this.add(this._dateTzOffset(), 'm'); - } - } - return this; - }, - - format : function (inputString) { - var output = formatMoment(this, inputString || moment.defaultFormat); - return this.localeData().postformat(output); - }, - - add : createAdder(1, 'add'), - - subtract : createAdder(-1, 'subtract'), - - diff : function (input, units, asFloat) { - var that = makeAs(input, this), - zoneDiff = (this.zone() - that.zone()) * 6e4, - diff, output, daysAdjust; - - units = normalizeUnits(units); - - if (units === 'year' || units === 'month') { - // average number of days in the months in the given dates - diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2 - // difference in months - output = ((this.year() - that.year()) * 12) + (this.month() - that.month()); - // adjust by taking difference in days, average number of days - // and dst in the given months. - daysAdjust = (this - moment(this).startOf('month')) - - (that - moment(that).startOf('month')); - // same as above but with zones, to negate all dst - daysAdjust -= ((this.zone() - moment(this).startOf('month').zone()) - - (that.zone() - moment(that).startOf('month').zone())) * 6e4; - output += daysAdjust / diff; - if (units === 'year') { - output = output / 12; - } - } else { - diff = (this - that); - output = units === 'second' ? diff / 1e3 : // 1000 - units === 'minute' ? diff / 6e4 : // 1000 * 60 - units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 - units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst - units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst - diff; - } - return asFloat ? output : absRound(output); - }, - - from : function (time, withoutSuffix) { - return moment.duration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); - }, - - fromNow : function (withoutSuffix) { - return this.from(moment(), withoutSuffix); - }, - - calendar : function (time) { - // We want to compare the start of today, vs this. - // Getting start-of-today depends on whether we're zone'd or not. - var now = time || moment(), - sod = makeAs(now, this).startOf('day'), - diff = this.diff(sod, 'days', true), - format = diff < -6 ? 'sameElse' : - diff < -1 ? 'lastWeek' : - diff < 0 ? 'lastDay' : - diff < 1 ? 'sameDay' : - diff < 2 ? 'nextDay' : - diff < 7 ? 'nextWeek' : 'sameElse'; - return this.format(this.localeData().calendar(format, this)); - }, - - isLeapYear : function () { - return isLeapYear(this.year()); - }, - - isDST : function () { - return (this.zone() < this.clone().month(0).zone() || - this.zone() < this.clone().month(5).zone()); - }, - - day : function (input) { - var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); - if (input != null) { - input = parseWeekday(input, this.localeData()); - return this.add(input - day, 'd'); - } else { - return day; - } - }, - - month : makeAccessor('Month', true), - - startOf : function (units) { - units = normalizeUnits(units); - // the following switch intentionally omits break keywords - // to utilize falling through the cases. - switch (units) { - case 'year': - this.month(0); - /* falls through */ - case 'quarter': - case 'month': - this.date(1); - /* falls through */ - case 'week': - case 'isoWeek': - case 'day': - this.hours(0); - /* falls through */ - case 'hour': - this.minutes(0); - /* falls through */ - case 'minute': - this.seconds(0); - /* falls through */ - case 'second': - this.milliseconds(0); - /* falls through */ - } - - // weeks are a special case - if (units === 'week') { - this.weekday(0); - } else if (units === 'isoWeek') { - this.isoWeekday(1); - } - - // quarters are also special - if (units === 'quarter') { - this.month(Math.floor(this.month() / 3) * 3); - } - - return this; - }, - - endOf: function (units) { - units = normalizeUnits(units); - return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); - }, - - isAfter: function (input, units) { - units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); - if (units === 'millisecond') { - input = moment.isMoment(input) ? input : moment(input); - return +this > +input; - } else { - return +this.clone().startOf(units) > +moment(input).startOf(units); - } - }, - - isBefore: function (input, units) { - units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); - if (units === 'millisecond') { - input = moment.isMoment(input) ? input : moment(input); - return +this < +input; - } else { - return +this.clone().startOf(units) < +moment(input).startOf(units); - } - }, - - isSame: function (input, units) { - units = normalizeUnits(units || 'millisecond'); - if (units === 'millisecond') { - input = moment.isMoment(input) ? input : moment(input); - return +this === +input; - } else { - return +this.clone().startOf(units) === +makeAs(input, this).startOf(units); - } - }, - - min: deprecate( - 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548', - function (other) { - other = moment.apply(null, arguments); - return other < this ? this : other; - } - ), - - max: deprecate( - 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548', - function (other) { - other = moment.apply(null, arguments); - return other > this ? this : other; - } - ), - - // keepLocalTime = true means only change the timezone, without - // affecting the local hour. So 5:31:26 +0300 --[zone(2, true)]--> - // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist int zone - // +0200, so we adjust the time as needed, to be valid. - // - // Keeping the time actually adds/subtracts (one hour) - // from the actual represented time. That is why we call updateOffset - // a second time. In case it wants us to change the offset again - // _changeInProgress == true case, then we have to adjust, because - // there is no such time in the given timezone. - zone : function (input, keepLocalTime) { - var offset = this._offset || 0, - localAdjust; - if (input != null) { - if (typeof input === 'string') { - input = timezoneMinutesFromString(input); - } - if (Math.abs(input) < 16) { - input = input * 60; - } - if (!this._isUTC && keepLocalTime) { - localAdjust = this._dateTzOffset(); - } - this._offset = input; - this._isUTC = true; - if (localAdjust != null) { - this.subtract(localAdjust, 'm'); - } - if (offset !== input) { - if (!keepLocalTime || this._changeInProgress) { - addOrSubtractDurationFromMoment(this, - moment.duration(offset - input, 'm'), 1, false); - } else if (!this._changeInProgress) { - this._changeInProgress = true; - moment.updateOffset(this, true); - this._changeInProgress = null; - } - } - } else { - return this._isUTC ? offset : this._dateTzOffset(); - } - return this; - }, - - zoneAbbr : function () { - return this._isUTC ? 'UTC' : ''; - }, - - zoneName : function () { - return this._isUTC ? 'Coordinated Universal Time' : ''; - }, - - parseZone : function () { - if (this._tzm) { - this.zone(this._tzm); - } else if (typeof this._i === 'string') { - this.zone(this._i); - } - return this; - }, - - hasAlignedHourOffset : function (input) { - if (!input) { - input = 0; - } - else { - input = moment(input).zone(); - } - - return (this.zone() - input) % 60 === 0; - }, - - daysInMonth : function () { - return daysInMonth(this.year(), this.month()); - }, - - dayOfYear : function (input) { - var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; - return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); - }, - - quarter : function (input) { - return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); - }, - - weekYear : function (input) { - var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year; - return input == null ? year : this.add((input - year), 'y'); - }, - - isoWeekYear : function (input) { - var year = weekOfYear(this, 1, 4).year; - return input == null ? year : this.add((input - year), 'y'); - }, - - week : function (input) { - var week = this.localeData().week(this); - return input == null ? week : this.add((input - week) * 7, 'd'); - }, - - isoWeek : function (input) { - var week = weekOfYear(this, 1, 4).week; - return input == null ? week : this.add((input - week) * 7, 'd'); - }, - - weekday : function (input) { - var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; - return input == null ? weekday : this.add(input - weekday, 'd'); - }, - - isoWeekday : function (input) { - // behaves the same as moment#day except - // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) - // as a setter, sunday should belong to the previous week. - return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); - }, - - isoWeeksInYear : function () { - return weeksInYear(this.year(), 1, 4); - }, - - weeksInYear : function () { - var weekInfo = this.localeData()._week; - return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); - }, - - get : function (units) { - units = normalizeUnits(units); - return this[units](); - }, - - set : function (units, value) { - units = normalizeUnits(units); - if (typeof this[units] === 'function') { - this[units](value); - } - return this; - }, - - // If passed a locale key, it will set the locale for this - // instance. Otherwise, it will return the locale configuration - // variables for this instance. - locale : function (key) { - var newLocaleData; - - if (key === undefined) { - return this._locale._abbr; - } else { - newLocaleData = moment.localeData(key); - if (newLocaleData != null) { - this._locale = newLocaleData; - } - return this; - } - }, - - lang : deprecate( - 'moment().lang() is deprecated. Use moment().localeData() instead.', - function (key) { - if (key === undefined) { - return this.localeData(); - } else { - return this.locale(key); - } - } - ), - - localeData : function () { - return this._locale; - }, - - _dateTzOffset : function () { - // On Firefox.24 Date#getTimezoneOffset returns a floating point. - // https://github.com/moment/moment/pull/1871 - return Math.round(this._d.getTimezoneOffset() / 15) * 15; - } - }); - - function rawMonthSetter(mom, value) { - var dayOfMonth; - - // TODO: Move this out of here! - if (typeof value === 'string') { - value = mom.localeData().monthsParse(value); - // TODO: Another silent failure? - if (typeof value !== 'number') { - return mom; - } - } - - dayOfMonth = Math.min(mom.date(), - daysInMonth(mom.year(), value)); - mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); - return mom; - } - - function rawGetter(mom, unit) { - return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); - } - - function rawSetter(mom, unit, value) { - if (unit === 'Month') { - return rawMonthSetter(mom, value); - } else { - return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); - } - } - - function makeAccessor(unit, keepTime) { - return function (value) { - if (value != null) { - rawSetter(this, unit, value); - moment.updateOffset(this, keepTime); - return this; - } else { - return rawGetter(this, unit); - } - }; - } - - moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false); - moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false); - moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false); - // Setting the hour should keep the time, because the user explicitly - // specified which hour he wants. So trying to maintain the same hour (in - // a new timezone) makes sense. Adding/subtracting hours does not follow - // this rule. - moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true); - // moment.fn.month is defined separately - moment.fn.date = makeAccessor('Date', true); - moment.fn.dates = deprecate('dates accessor is deprecated. Use date instead.', makeAccessor('Date', true)); - moment.fn.year = makeAccessor('FullYear', true); - moment.fn.years = deprecate('years accessor is deprecated. Use year instead.', makeAccessor('FullYear', true)); - - // add plural methods - moment.fn.days = moment.fn.day; - moment.fn.months = moment.fn.month; - moment.fn.weeks = moment.fn.week; - moment.fn.isoWeeks = moment.fn.isoWeek; - moment.fn.quarters = moment.fn.quarter; - - // add aliased format methods - moment.fn.toJSON = moment.fn.toISOString; - - /************************************ - Duration Prototype - ************************************/ - - - function daysToYears (days) { - // 400 years have 146097 days (taking into account leap year rules) - return days * 400 / 146097; - } - - function yearsToDays (years) { - // years * 365 + absRound(years / 4) - - // absRound(years / 100) + absRound(years / 400); - return years * 146097 / 400; - } - - extend(moment.duration.fn = Duration.prototype, { - - _bubble : function () { - var milliseconds = this._milliseconds, - days = this._days, - months = this._months, - data = this._data, - seconds, minutes, hours, years = 0; - - // The following code bubbles up values, see the tests for - // examples of what that means. - data.milliseconds = milliseconds % 1000; - - seconds = absRound(milliseconds / 1000); - data.seconds = seconds % 60; - - minutes = absRound(seconds / 60); - data.minutes = minutes % 60; - - hours = absRound(minutes / 60); - data.hours = hours % 24; - - days += absRound(hours / 24); - - // Accurately convert days to years, assume start from year 0. - years = absRound(daysToYears(days)); - days -= absRound(yearsToDays(years)); - - // 30 days to a month - // TODO (iskren): Use anchor date (like 1st Jan) to compute this. - months += absRound(days / 30); - days %= 30; - - // 12 months -> 1 year - years += absRound(months / 12); - months %= 12; - - data.days = days; - data.months = months; - data.years = years; - }, - - abs : function () { - this._milliseconds = Math.abs(this._milliseconds); - this._days = Math.abs(this._days); - this._months = Math.abs(this._months); - - this._data.milliseconds = Math.abs(this._data.milliseconds); - this._data.seconds = Math.abs(this._data.seconds); - this._data.minutes = Math.abs(this._data.minutes); - this._data.hours = Math.abs(this._data.hours); - this._data.months = Math.abs(this._data.months); - this._data.years = Math.abs(this._data.years); - - return this; - }, - - weeks : function () { - return absRound(this.days() / 7); - }, - - valueOf : function () { - return this._milliseconds + - this._days * 864e5 + - (this._months % 12) * 2592e6 + - toInt(this._months / 12) * 31536e6; - }, - - humanize : function (withSuffix) { - var output = relativeTime(this, !withSuffix, this.localeData()); - - if (withSuffix) { - output = this.localeData().pastFuture(+this, output); - } - - return this.localeData().postformat(output); - }, - - add : function (input, val) { - // supports only 2.0-style add(1, 's') or add(moment) - var dur = moment.duration(input, val); - - this._milliseconds += dur._milliseconds; - this._days += dur._days; - this._months += dur._months; - - this._bubble(); - - return this; - }, - - subtract : function (input, val) { - var dur = moment.duration(input, val); - - this._milliseconds -= dur._milliseconds; - this._days -= dur._days; - this._months -= dur._months; - - this._bubble(); - - return this; - }, - - get : function (units) { - units = normalizeUnits(units); - return this[units.toLowerCase() + 's'](); - }, - - as : function (units) { - var days, months; - units = normalizeUnits(units); - - if (units === 'month' || units === 'year') { - days = this._days + this._milliseconds / 864e5; - months = this._months + daysToYears(days) * 12; - return units === 'month' ? months : months / 12; - } else { - // handle milliseconds separately because of floating point math errors (issue #1867) - days = this._days + yearsToDays(this._months / 12); - switch (units) { - case 'week': return days / 7 + this._milliseconds / 6048e5; - case 'day': return days + this._milliseconds / 864e5; - case 'hour': return days * 24 + this._milliseconds / 36e5; - case 'minute': return days * 24 * 60 + this._milliseconds / 6e4; - case 'second': return days * 24 * 60 * 60 + this._milliseconds / 1000; - // Math.floor prevents floating point math errors here - case 'millisecond': return Math.floor(days * 24 * 60 * 60 * 1000) + this._milliseconds; - default: throw new Error('Unknown unit ' + units); - } - } - }, - - lang : moment.fn.lang, - locale : moment.fn.locale, - - toIsoString : deprecate( - 'toIsoString() is deprecated. Please use toISOString() instead ' + - '(notice the capitals)', - function () { - return this.toISOString(); - } - ), - - toISOString : function () { - // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js - var years = Math.abs(this.years()), - months = Math.abs(this.months()), - days = Math.abs(this.days()), - hours = Math.abs(this.hours()), - minutes = Math.abs(this.minutes()), - seconds = Math.abs(this.seconds() + this.milliseconds() / 1000); - - if (!this.asSeconds()) { - // this is the same as C#'s (Noda) and python (isodate)... - // but not other JS (goog.date) - return 'P0D'; - } - - return (this.asSeconds() < 0 ? '-' : '') + - 'P' + - (years ? years + 'Y' : '') + - (months ? months + 'M' : '') + - (days ? days + 'D' : '') + - ((hours || minutes || seconds) ? 'T' : '') + - (hours ? hours + 'H' : '') + - (minutes ? minutes + 'M' : '') + - (seconds ? seconds + 'S' : ''); - }, - - localeData : function () { - return this._locale; - } - }); - - moment.duration.fn.toString = moment.duration.fn.toISOString; - - function makeDurationGetter(name) { - moment.duration.fn[name] = function () { - return this._data[name]; - }; - } - - for (i in unitMillisecondFactors) { - if (hasOwnProp(unitMillisecondFactors, i)) { - makeDurationGetter(i.toLowerCase()); - } - } - - moment.duration.fn.asMilliseconds = function () { - return this.as('ms'); - }; - moment.duration.fn.asSeconds = function () { - return this.as('s'); - }; - moment.duration.fn.asMinutes = function () { - return this.as('m'); - }; - moment.duration.fn.asHours = function () { - return this.as('h'); - }; - moment.duration.fn.asDays = function () { - return this.as('d'); - }; - moment.duration.fn.asWeeks = function () { - return this.as('weeks'); - }; - moment.duration.fn.asMonths = function () { - return this.as('M'); - }; - moment.duration.fn.asYears = function () { - return this.as('y'); - }; - - /************************************ - Default Locale - ************************************/ - - - // Set default locale, other locale will inherit from English. - moment.locale('en', { - ordinal : function (number) { - var b = number % 10, - output = (toInt(number % 100 / 10) === 1) ? 'th' : - (b === 1) ? 'st' : - (b === 2) ? 'nd' : - (b === 3) ? 'rd' : 'th'; - return number + output; - } - }); - - // moment.js locale configuration -// locale : afrikaans (af) -// author : Werner Mollentze : https://github.com/wernerm - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('af', { - months : 'Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember'.split('_'), - monthsShort : 'Jan_Feb_Mar_Apr_Mei_Jun_Jul_Aug_Sep_Okt_Nov_Des'.split('_'), - weekdays : 'Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag'.split('_'), - weekdaysShort : 'Son_Maa_Din_Woe_Don_Vry_Sat'.split('_'), - weekdaysMin : 'So_Ma_Di_Wo_Do_Vr_Sa'.split('_'), - meridiem : function (hours, minutes, isLower) { - if (hours < 12) { - return isLower ? 'vm' : 'VM'; - } else { - return isLower ? 'nm' : 'NM'; - } - }, - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd, D MMMM YYYY LT' - }, - calendar : { - sameDay : '[Vandag om] LT', - nextDay : '[Môre om] LT', - nextWeek : 'dddd [om] LT', - lastDay : '[Gister om] LT', - lastWeek : '[Laas] dddd [om] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'oor %s', - past : '%s gelede', - s : '\'n paar sekondes', - m : '\'n minuut', - mm : '%d minute', - h : '\'n uur', - hh : '%d ure', - d : '\'n dag', - dd : '%d dae', - M : '\'n maand', - MM : '%d maande', - y : '\'n jaar', - yy : '%d jaar' - }, - ordinal : function (number) { - return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); // Thanks to Joris Röling : https://github.com/jjupiter - }, - week : { - dow : 1, // Maandag is die eerste dag van die week. - doy : 4 // Die week wat die 4de Januarie bevat is die eerste week van die jaar. - } - }); -})); -// moment.js locale configuration -// locale : Moroccan Arabic (ar-ma) -// author : ElFadili Yassine : https://github.com/ElFadiliY -// author : Abdel Said : https://github.com/abdelsaid - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('ar-ma', { - months : 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'), - monthsShort : 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'), - weekdays : 'الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), - weekdaysShort : 'احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'), - weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd D MMMM YYYY LT' - }, - calendar : { - sameDay: '[اليوم على الساعة] LT', - nextDay: '[غدا على الساعة] LT', - nextWeek: 'dddd [على الساعة] LT', - lastDay: '[أمس على الساعة] LT', - lastWeek: 'dddd [على الساعة] LT', - sameElse: 'L' - }, - relativeTime : { - future : 'في %s', - past : 'منذ %s', - s : 'ثوان', - m : 'دقيقة', - mm : '%d دقائق', - h : 'ساعة', - hh : '%d ساعات', - d : 'يوم', - dd : '%d أيام', - M : 'شهر', - MM : '%d أشهر', - y : 'سنة', - yy : '%d سنوات' - }, - week : { - dow : 6, // Saturday is the first day of the week. - doy : 12 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Arabic Saudi Arabia (ar-sa) -// author : Suhail Alkowaileet : https://github.com/xsoh - -(function (factory) { - factory(moment); -}(function (moment) { - var symbolMap = { - '1': '١', - '2': '٢', - '3': '٣', - '4': '٤', - '5': '٥', - '6': '٦', - '7': '٧', - '8': '٨', - '9': '٩', - '0': '٠' - }, numberMap = { - '١': '1', - '٢': '2', - '٣': '3', - '٤': '4', - '٥': '5', - '٦': '6', - '٧': '7', - '٨': '8', - '٩': '9', - '٠': '0' - }; - - return moment.defineLocale('ar-sa', { - months : 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), - monthsShort : 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), - weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), - weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'), - weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd D MMMM YYYY LT' - }, - meridiem : function (hour, minute, isLower) { - if (hour < 12) { - return 'ص'; - } else { - return 'م'; - } - }, - calendar : { - sameDay: '[اليوم على الساعة] LT', - nextDay: '[غدا على الساعة] LT', - nextWeek: 'dddd [على الساعة] LT', - lastDay: '[أمس على الساعة] LT', - lastWeek: 'dddd [على الساعة] LT', - sameElse: 'L' - }, - relativeTime : { - future : 'في %s', - past : 'منذ %s', - s : 'ثوان', - m : 'دقيقة', - mm : '%d دقائق', - h : 'ساعة', - hh : '%d ساعات', - d : 'يوم', - dd : '%d أيام', - M : 'شهر', - MM : '%d أشهر', - y : 'سنة', - yy : '%d سنوات' - }, - preparse: function (string) { - return string.replace(/[۰-۹]/g, function (match) { - return numberMap[match]; - }).replace(/،/g, ','); - }, - postformat: function (string) { - return string.replace(/\d/g, function (match) { - return symbolMap[match]; - }).replace(/,/g, '،'); - }, - week : { - dow : 6, // Saturday is the first day of the week. - doy : 12 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// Locale: Arabic (ar) -// Author: Abdel Said: https://github.com/abdelsaid -// Changes in months, weekdays: Ahmed Elkhatib -// Native plural forms: forabi https://github.com/forabi - -(function (factory) { - factory(moment); -}(function (moment) { - var symbolMap = { - '1': '١', - '2': '٢', - '3': '٣', - '4': '٤', - '5': '٥', - '6': '٦', - '7': '٧', - '8': '٨', - '9': '٩', - '0': '٠' - }, numberMap = { - '١': '1', - '٢': '2', - '٣': '3', - '٤': '4', - '٥': '5', - '٦': '6', - '٧': '7', - '٨': '8', - '٩': '9', - '٠': '0' - }, pluralForm = function (n) { - return n === 0 ? 0 : n === 1 ? 1 : n === 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5; - }, plurals = { - s : ['أقل من ثانية', 'ثانية واحدة', ['ثانيتان', 'ثانيتين'], '%d ثوان', '%d ثانية', '%d ثانية'], - m : ['أقل من دقيقة', 'دقيقة واحدة', ['دقيقتان', 'دقيقتين'], '%d دقائق', '%d دقيقة', '%d دقيقة'], - h : ['أقل من ساعة', 'ساعة واحدة', ['ساعتان', 'ساعتين'], '%d ساعات', '%d ساعة', '%d ساعة'], - d : ['أقل من يوم', 'يوم واحد', ['يومان', 'يومين'], '%d أيام', '%d يومًا', '%d يوم'], - M : ['أقل من شهر', 'شهر واحد', ['شهران', 'شهرين'], '%d أشهر', '%d شهرا', '%d شهر'], - y : ['أقل من عام', 'عام واحد', ['عامان', 'عامين'], '%d أعوام', '%d عامًا', '%d عام'] - }, pluralize = function (u) { - return function (number, withoutSuffix, string, isFuture) { - var f = pluralForm(number), - str = plurals[u][pluralForm(number)]; - if (f === 2) { - str = str[withoutSuffix ? 0 : 1]; - } - return str.replace(/%d/i, number); - }; - }, months = [ - 'كانون الثاني يناير', - 'شباط فبراير', - 'آذار مارس', - 'نيسان أبريل', - 'أيار مايو', - 'حزيران يونيو', - 'تموز يوليو', - 'آب أغسطس', - 'أيلول سبتمبر', - 'تشرين الأول أكتوبر', - 'تشرين الثاني نوفمبر', - 'كانون الأول ديسمبر' - ]; - - return moment.defineLocale('ar', { - months : months, - monthsShort : months, - weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), - weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'), - weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd D MMMM YYYY LT' - }, - meridiem : function (hour, minute, isLower) { - if (hour < 12) { - return 'ص'; - } else { - return 'م'; - } - }, - calendar : { - sameDay: '[اليوم عند الساعة] LT', - nextDay: '[غدًا عند الساعة] LT', - nextWeek: 'dddd [عند الساعة] LT', - lastDay: '[أمس عند الساعة] LT', - lastWeek: 'dddd [عند الساعة] LT', - sameElse: 'L' - }, - relativeTime : { - future : 'بعد %s', - past : 'منذ %s', - s : pluralize('s'), - m : pluralize('m'), - mm : pluralize('m'), - h : pluralize('h'), - hh : pluralize('h'), - d : pluralize('d'), - dd : pluralize('d'), - M : pluralize('M'), - MM : pluralize('M'), - y : pluralize('y'), - yy : pluralize('y') - }, - preparse: function (string) { - return string.replace(/[۰-۹]/g, function (match) { - return numberMap[match]; - }).replace(/،/g, ','); - }, - postformat: function (string) { - return string.replace(/\d/g, function (match) { - return symbolMap[match]; - }).replace(/,/g, '،'); - }, - week : { - dow : 6, // Saturday is the first day of the week. - doy : 12 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : azerbaijani (az) -// author : topchiyev : https://github.com/topchiyev - -(function (factory) { - factory(moment); -}(function (moment) { - var suffixes = { - 1: '-inci', - 5: '-inci', - 8: '-inci', - 70: '-inci', - 80: '-inci', - - 2: '-nci', - 7: '-nci', - 20: '-nci', - 50: '-nci', - - 3: '-üncü', - 4: '-üncü', - 100: '-üncü', - - 6: '-ncı', - - 9: '-uncu', - 10: '-uncu', - 30: '-uncu', - - 60: '-ıncı', - 90: '-ıncı' - }; - return moment.defineLocale('az', { - months : 'yanvar_fevral_mart_aprel_may_iyun_iyul_avqust_sentyabr_oktyabr_noyabr_dekabr'.split('_'), - monthsShort : 'yan_fev_mar_apr_may_iyn_iyl_avq_sen_okt_noy_dek'.split('_'), - weekdays : 'Bazar_Bazar ertəsi_Çərşənbə axşamı_Çərşənbə_Cümə axşamı_Cümə_Şənbə'.split('_'), - weekdaysShort : 'Baz_BzE_ÇAx_Çər_CAx_Cüm_Şən'.split('_'), - weekdaysMin : 'Bz_BE_ÇA_Çə_CA_Cü_Şə'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD.MM.YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd, D MMMM YYYY LT' - }, - calendar : { - sameDay : '[bugün saat] LT', - nextDay : '[sabah saat] LT', - nextWeek : '[gələn həftə] dddd [saat] LT', - lastDay : '[dünən] LT', - lastWeek : '[keçən həftə] dddd [saat] LT', - sameElse : 'L' - }, - relativeTime : { - future : '%s sonra', - past : '%s əvvəl', - s : 'birneçə saniyyə', - m : 'bir dəqiqə', - mm : '%d dəqiqə', - h : 'bir saat', - hh : '%d saat', - d : 'bir gün', - dd : '%d gün', - M : 'bir ay', - MM : '%d ay', - y : 'bir il', - yy : '%d il' - }, - meridiem : function (hour, minute, isLower) { - if (hour < 4) { - return 'gecə'; - } else if (hour < 12) { - return 'səhər'; - } else if (hour < 17) { - return 'gündüz'; - } else { - return 'axşam'; - } - }, - ordinal : function (number) { - if (number === 0) { // special case for zero - return number + '-ıncı'; - } - var a = number % 10, - b = number % 100 - a, - c = number >= 100 ? 100 : null; - - return number + (suffixes[a] || suffixes[b] || suffixes[c]); - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : belarusian (be) -// author : Dmitry Demidov : https://github.com/demidov91 -// author: Praleska: http://praleska.pro/ -// Author : Menelion Elensúle : https://github.com/Oire - -(function (factory) { - factory(moment); -}(function (moment) { - function plural(word, num) { - var forms = word.split('_'); - return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]); - } - - function relativeTimeWithPlural(number, withoutSuffix, key) { - var format = { - 'mm': withoutSuffix ? 'хвіліна_хвіліны_хвілін' : 'хвіліну_хвіліны_хвілін', - 'hh': withoutSuffix ? 'гадзіна_гадзіны_гадзін' : 'гадзіну_гадзіны_гадзін', - 'dd': 'дзень_дні_дзён', - 'MM': 'месяц_месяцы_месяцаў', - 'yy': 'год_гады_гадоў' - }; - if (key === 'm') { - return withoutSuffix ? 'хвіліна' : 'хвіліну'; - } - else if (key === 'h') { - return withoutSuffix ? 'гадзіна' : 'гадзіну'; - } - else { - return number + ' ' + plural(format[key], +number); - } - } - - function monthsCaseReplace(m, format) { - var months = { - 'nominative': 'студзень_люты_сакавік_красавік_травень_чэрвень_ліпень_жнівень_верасень_кастрычнік_лістапад_снежань'.split('_'), - 'accusative': 'студзеня_лютага_сакавіка_красавіка_траўня_чэрвеня_ліпеня_жніўня_верасня_кастрычніка_лістапада_снежня'.split('_') - }, - - nounCase = (/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/).test(format) ? - 'accusative' : - 'nominative'; - - return months[nounCase][m.month()]; - } - - function weekdaysCaseReplace(m, format) { - var weekdays = { - 'nominative': 'нядзеля_панядзелак_аўторак_серада_чацвер_пятніца_субота'.split('_'), - 'accusative': 'нядзелю_панядзелак_аўторак_сераду_чацвер_пятніцу_суботу'.split('_') - }, - - nounCase = (/\[ ?[Вв] ?(?:мінулую|наступную)? ?\] ?dddd/).test(format) ? - 'accusative' : - 'nominative'; - - return weekdays[nounCase][m.day()]; - } - - return moment.defineLocale('be', { - months : monthsCaseReplace, - monthsShort : 'студ_лют_сак_крас_трав_чэрв_ліп_жнів_вер_каст_ліст_снеж'.split('_'), - weekdays : weekdaysCaseReplace, - weekdaysShort : 'нд_пн_ат_ср_чц_пт_сб'.split('_'), - weekdaysMin : 'нд_пн_ат_ср_чц_пт_сб'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD.MM.YYYY', - LL : 'D MMMM YYYY г.', - LLL : 'D MMMM YYYY г., LT', - LLLL : 'dddd, D MMMM YYYY г., LT' - }, - calendar : { - sameDay: '[Сёння ў] LT', - nextDay: '[Заўтра ў] LT', - lastDay: '[Учора ў] LT', - nextWeek: function () { - return '[У] dddd [ў] LT'; - }, - lastWeek: function () { - switch (this.day()) { - case 0: - case 3: - case 5: - case 6: - return '[У мінулую] dddd [ў] LT'; - case 1: - case 2: - case 4: - return '[У мінулы] dddd [ў] LT'; - } - }, - sameElse: 'L' - }, - relativeTime : { - future : 'праз %s', - past : '%s таму', - s : 'некалькі секунд', - m : relativeTimeWithPlural, - mm : relativeTimeWithPlural, - h : relativeTimeWithPlural, - hh : relativeTimeWithPlural, - d : 'дзень', - dd : relativeTimeWithPlural, - M : 'месяц', - MM : relativeTimeWithPlural, - y : 'год', - yy : relativeTimeWithPlural - }, - - - meridiem : function (hour, minute, isLower) { - if (hour < 4) { - return 'ночы'; - } else if (hour < 12) { - return 'раніцы'; - } else if (hour < 17) { - return 'дня'; - } else { - return 'вечара'; - } - }, - - ordinal: function (number, period) { - switch (period) { - case 'M': - case 'd': - case 'DDD': - case 'w': - case 'W': - return (number % 10 === 2 || number % 10 === 3) && (number % 100 !== 12 && number % 100 !== 13) ? number + '-і' : number + '-ы'; - case 'D': - return number + '-га'; - default: - return number; - } - }, - - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : bulgarian (bg) -// author : Krasen Borisov : https://github.com/kraz - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('bg', { - months : 'януари_февруари_март_април_май_юни_юли_август_септември_октомври_ноември_декември'.split('_'), - monthsShort : 'янр_фев_мар_апр_май_юни_юли_авг_сеп_окт_ное_дек'.split('_'), - weekdays : 'неделя_понеделник_вторник_сряда_четвъртък_петък_събота'.split('_'), - weekdaysShort : 'нед_пон_вто_сря_чет_пет_съб'.split('_'), - weekdaysMin : 'нд_пн_вт_ср_чт_пт_сб'.split('_'), - longDateFormat : { - LT : 'H:mm', - L : 'D.MM.YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd, D MMMM YYYY LT' - }, - calendar : { - sameDay : '[Днес в] LT', - nextDay : '[Утре в] LT', - nextWeek : 'dddd [в] LT', - lastDay : '[Вчера в] LT', - lastWeek : function () { - switch (this.day()) { - case 0: - case 3: - case 6: - return '[В изминалата] dddd [в] LT'; - case 1: - case 2: - case 4: - case 5: - return '[В изминалия] dddd [в] LT'; - } - }, - sameElse : 'L' - }, - relativeTime : { - future : 'след %s', - past : 'преди %s', - s : 'няколко секунди', - m : 'минута', - mm : '%d минути', - h : 'час', - hh : '%d часа', - d : 'ден', - dd : '%d дни', - M : 'месец', - MM : '%d месеца', - y : 'година', - yy : '%d години' - }, - ordinal : function (number) { - var lastDigit = number % 10, - last2Digits = number % 100; - if (number === 0) { - return number + '-ев'; - } else if (last2Digits === 0) { - return number + '-ен'; - } else if (last2Digits > 10 && last2Digits < 20) { - return number + '-ти'; - } else if (lastDigit === 1) { - return number + '-ви'; - } else if (lastDigit === 2) { - return number + '-ри'; - } else if (lastDigit === 7 || lastDigit === 8) { - return number + '-ми'; - } else { - return number + '-ти'; - } - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Bengali (bn) -// author : Kaushik Gandhi : https://github.com/kaushikgandhi - -(function (factory) { - factory(moment); -}(function (moment) { - var symbolMap = { - '1': '১', - '2': '২', - '3': '৩', - '4': '৪', - '5': '৫', - '6': '৬', - '7': '৭', - '8': '৮', - '9': '৯', - '0': '০' - }, - numberMap = { - '১': '1', - '২': '2', - '৩': '3', - '৪': '4', - '৫': '5', - '৬': '6', - '৭': '7', - '৮': '8', - '৯': '9', - '০': '0' - }; - - return moment.defineLocale('bn', { - months : 'জানুয়ারী_ফেবুয়ারী_মার্চ_এপ্রিল_মে_জুন_জুলাই_অগাস্ট_সেপ্টেম্বর_অক্টোবর_নভেম্বর_ডিসেম্বর'.split('_'), - monthsShort : 'জানু_ফেব_মার্চ_এপর_মে_জুন_জুল_অগ_সেপ্ট_অক্টো_নভ_ডিসেম্'.split('_'), - weekdays : 'রবিবার_সোমবার_মঙ্গলবার_বুধবার_বৃহস্পত্তিবার_শুক্রুবার_শনিবার'.split('_'), - weekdaysShort : 'রবি_সোম_মঙ্গল_বুধ_বৃহস্পত্তি_শুক্রু_শনি'.split('_'), - weekdaysMin : 'রব_সম_মঙ্গ_বু_ব্রিহ_শু_শনি'.split('_'), - longDateFormat : { - LT : 'A h:mm সময়', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY, LT', - LLLL : 'dddd, D MMMM YYYY, LT' - }, - calendar : { - sameDay : '[আজ] LT', - nextDay : '[আগামীকাল] LT', - nextWeek : 'dddd, LT', - lastDay : '[গতকাল] LT', - lastWeek : '[গত] dddd, LT', - sameElse : 'L' - }, - relativeTime : { - future : '%s পরে', - past : '%s আগে', - s : 'কএক সেকেন্ড', - m : 'এক মিনিট', - mm : '%d মিনিট', - h : 'এক ঘন্টা', - hh : '%d ঘন্টা', - d : 'এক দিন', - dd : '%d দিন', - M : 'এক মাস', - MM : '%d মাস', - y : 'এক বছর', - yy : '%d বছর' - }, - preparse: function (string) { - return string.replace(/[১২৩৪৫৬৭৮৯০]/g, function (match) { - return numberMap[match]; - }); - }, - postformat: function (string) { - return string.replace(/\d/g, function (match) { - return symbolMap[match]; - }); - }, - //Bengali is a vast language its spoken - //in different forms in various parts of the world. - //I have just generalized with most common one used - meridiem : function (hour, minute, isLower) { - if (hour < 4) { - return 'রাত'; - } else if (hour < 10) { - return 'শকাল'; - } else if (hour < 17) { - return 'দুপুর'; - } else if (hour < 20) { - return 'বিকেল'; - } else { - return 'রাত'; - } - }, - week : { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : tibetan (bo) -// author : Thupten N. Chakrishar : https://github.com/vajradog - -(function (factory) { - factory(moment); -}(function (moment) { - var symbolMap = { - '1': '༡', - '2': '༢', - '3': '༣', - '4': '༤', - '5': '༥', - '6': '༦', - '7': '༧', - '8': '༨', - '9': '༩', - '0': '༠' - }, - numberMap = { - '༡': '1', - '༢': '2', - '༣': '3', - '༤': '4', - '༥': '5', - '༦': '6', - '༧': '7', - '༨': '8', - '༩': '9', - '༠': '0' - }; - - return moment.defineLocale('bo', { - months : 'ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ'.split('_'), - monthsShort : 'ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ'.split('_'), - weekdays : 'གཟའ་ཉི་མ་_གཟའ་ཟླ་བ་_གཟའ་མིག་དམར་_གཟའ་ལྷག་པ་_གཟའ་ཕུར་བུ_གཟའ་པ་སངས་_གཟའ་སྤེན་པ་'.split('_'), - weekdaysShort : 'ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་'.split('_'), - weekdaysMin : 'ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་'.split('_'), - longDateFormat : { - LT : 'A h:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY, LT', - LLLL : 'dddd, D MMMM YYYY, LT' - }, - calendar : { - sameDay : '[དི་རིང] LT', - nextDay : '[སང་ཉིན] LT', - nextWeek : '[བདུན་ཕྲག་རྗེས་མ], LT', - lastDay : '[ཁ་སང] LT', - lastWeek : '[བདུན་ཕྲག་མཐའ་མ] dddd, LT', - sameElse : 'L' - }, - relativeTime : { - future : '%s ལ་', - past : '%s སྔན་ལ', - s : 'ལམ་སང', - m : 'སྐར་མ་གཅིག', - mm : '%d སྐར་མ', - h : 'ཆུ་ཚོད་གཅིག', - hh : '%d ཆུ་ཚོད', - d : 'ཉིན་གཅིག', - dd : '%d ཉིན་', - M : 'ཟླ་བ་གཅིག', - MM : '%d ཟླ་བ', - y : 'ལོ་གཅིག', - yy : '%d ལོ' - }, - preparse: function (string) { - return string.replace(/[༡༢༣༤༥༦༧༨༩༠]/g, function (match) { - return numberMap[match]; - }); - }, - postformat: function (string) { - return string.replace(/\d/g, function (match) { - return symbolMap[match]; - }); - }, - meridiem : function (hour, minute, isLower) { - if (hour < 4) { - return 'མཚན་མོ'; - } else if (hour < 10) { - return 'ཞོགས་ཀས'; - } else if (hour < 17) { - return 'ཉིན་གུང'; - } else if (hour < 20) { - return 'དགོང་དག'; - } else { - return 'མཚན་མོ'; - } - }, - week : { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : breton (br) -// author : Jean-Baptiste Le Duigou : https://github.com/jbleduigou - -(function (factory) { - factory(moment); -}(function (moment) { - function relativeTimeWithMutation(number, withoutSuffix, key) { - var format = { - 'mm': 'munutenn', - 'MM': 'miz', - 'dd': 'devezh' - }; - return number + ' ' + mutation(format[key], number); - } - - function specialMutationForYears(number) { - switch (lastNumber(number)) { - case 1: - case 3: - case 4: - case 5: - case 9: - return number + ' bloaz'; - default: - return number + ' vloaz'; - } - } - - function lastNumber(number) { - if (number > 9) { - return lastNumber(number % 10); - } - return number; - } - - function mutation(text, number) { - if (number === 2) { - return softMutation(text); - } - return text; - } - - function softMutation(text) { - var mutationTable = { - 'm': 'v', - 'b': 'v', - 'd': 'z' - }; - if (mutationTable[text.charAt(0)] === undefined) { - return text; - } - return mutationTable[text.charAt(0)] + text.substring(1); - } - - return moment.defineLocale('br', { - months : 'Genver_C\'hwevrer_Meurzh_Ebrel_Mae_Mezheven_Gouere_Eost_Gwengolo_Here_Du_Kerzu'.split('_'), - monthsShort : 'Gen_C\'hwe_Meu_Ebr_Mae_Eve_Gou_Eos_Gwe_Her_Du_Ker'.split('_'), - weekdays : 'Sul_Lun_Meurzh_Merc\'her_Yaou_Gwener_Sadorn'.split('_'), - weekdaysShort : 'Sul_Lun_Meu_Mer_Yao_Gwe_Sad'.split('_'), - weekdaysMin : 'Su_Lu_Me_Mer_Ya_Gw_Sa'.split('_'), - longDateFormat : { - LT : 'h[e]mm A', - L : 'DD/MM/YYYY', - LL : 'D [a viz] MMMM YYYY', - LLL : 'D [a viz] MMMM YYYY LT', - LLLL : 'dddd, D [a viz] MMMM YYYY LT' - }, - calendar : { - sameDay : '[Hiziv da] LT', - nextDay : '[Warc\'hoazh da] LT', - nextWeek : 'dddd [da] LT', - lastDay : '[Dec\'h da] LT', - lastWeek : 'dddd [paset da] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'a-benn %s', - past : '%s \'zo', - s : 'un nebeud segondennoù', - m : 'ur vunutenn', - mm : relativeTimeWithMutation, - h : 'un eur', - hh : '%d eur', - d : 'un devezh', - dd : relativeTimeWithMutation, - M : 'ur miz', - MM : relativeTimeWithMutation, - y : 'ur bloaz', - yy : specialMutationForYears - }, - ordinal : function (number) { - var output = (number === 1) ? 'añ' : 'vet'; - return number + output; - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : bosnian (bs) -// author : Nedim Cholich : https://github.com/frontyard -// based on (hr) translation by Bojan Marković - -(function (factory) { - factory(moment); -}(function (moment) { - function translate(number, withoutSuffix, key) { - var result = number + ' '; - switch (key) { - case 'm': - return withoutSuffix ? 'jedna minuta' : 'jedne minute'; - case 'mm': - if (number === 1) { - result += 'minuta'; - } else if (number === 2 || number === 3 || number === 4) { - result += 'minute'; - } else { - result += 'minuta'; - } - return result; - case 'h': - return withoutSuffix ? 'jedan sat' : 'jednog sata'; - case 'hh': - if (number === 1) { - result += 'sat'; - } else if (number === 2 || number === 3 || number === 4) { - result += 'sata'; - } else { - result += 'sati'; - } - return result; - case 'dd': - if (number === 1) { - result += 'dan'; - } else { - result += 'dana'; - } - return result; - case 'MM': - if (number === 1) { - result += 'mjesec'; - } else if (number === 2 || number === 3 || number === 4) { - result += 'mjeseca'; - } else { - result += 'mjeseci'; - } - return result; - case 'yy': - if (number === 1) { - result += 'godina'; - } else if (number === 2 || number === 3 || number === 4) { - result += 'godine'; - } else { - result += 'godina'; - } - return result; - } - } - - return moment.defineLocale('bs', { - months : 'januar_februar_mart_april_maj_juni_juli_avgust_septembar_oktobar_novembar_decembar'.split('_'), - monthsShort : 'jan._feb._mar._apr._maj._jun._jul._avg._sep._okt._nov._dec.'.split('_'), - weekdays : 'nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota'.split('_'), - weekdaysShort : 'ned._pon._uto._sri._čet._pet._sub.'.split('_'), - weekdaysMin : 'ne_po_ut_sr_če_pe_su'.split('_'), - longDateFormat : { - LT : 'H:mm', - L : 'DD. MM. YYYY', - LL : 'D. MMMM YYYY', - LLL : 'D. MMMM YYYY LT', - LLLL : 'dddd, D. MMMM YYYY LT' - }, - calendar : { - sameDay : '[danas u] LT', - nextDay : '[sutra u] LT', - - nextWeek : function () { - switch (this.day()) { - case 0: - return '[u] [nedjelju] [u] LT'; - case 3: - return '[u] [srijedu] [u] LT'; - case 6: - return '[u] [subotu] [u] LT'; - case 1: - case 2: - case 4: - case 5: - return '[u] dddd [u] LT'; - } - }, - lastDay : '[jučer u] LT', - lastWeek : function () { - switch (this.day()) { - case 0: - case 3: - return '[prošlu] dddd [u] LT'; - case 6: - return '[prošle] [subote] [u] LT'; - case 1: - case 2: - case 4: - case 5: - return '[prošli] dddd [u] LT'; - } - }, - sameElse : 'L' - }, - relativeTime : { - future : 'za %s', - past : 'prije %s', - s : 'par sekundi', - m : translate, - mm : translate, - h : translate, - hh : translate, - d : 'dan', - dd : translate, - M : 'mjesec', - MM : translate, - y : 'godinu', - yy : translate - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : catalan (ca) -// author : Juan G. Hurtado : https://github.com/juanghurtado - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('ca', { - months : 'gener_febrer_març_abril_maig_juny_juliol_agost_setembre_octubre_novembre_desembre'.split('_'), - monthsShort : 'gen._febr._mar._abr._mai._jun._jul._ag._set._oct._nov._des.'.split('_'), - weekdays : 'diumenge_dilluns_dimarts_dimecres_dijous_divendres_dissabte'.split('_'), - weekdaysShort : 'dg._dl._dt._dc._dj._dv._ds.'.split('_'), - weekdaysMin : 'Dg_Dl_Dt_Dc_Dj_Dv_Ds'.split('_'), - longDateFormat : { - LT : 'H:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd D MMMM YYYY LT' - }, - calendar : { - sameDay : function () { - return '[avui a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; - }, - nextDay : function () { - return '[demà a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; - }, - nextWeek : function () { - return 'dddd [a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; - }, - lastDay : function () { - return '[ahir a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; - }, - lastWeek : function () { - return '[el] dddd [passat a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; - }, - sameElse : 'L' - }, - relativeTime : { - future : 'en %s', - past : 'fa %s', - s : 'uns segons', - m : 'un minut', - mm : '%d minuts', - h : 'una hora', - hh : '%d hores', - d : 'un dia', - dd : '%d dies', - M : 'un mes', - MM : '%d mesos', - y : 'un any', - yy : '%d anys' - }, - ordinal : '%dº', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : czech (cs) -// author : petrbela : https://github.com/petrbela - -(function (factory) { - factory(moment); -}(function (moment) { - var months = 'leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec'.split('_'), - monthsShort = 'led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro'.split('_'); - - function plural(n) { - return (n > 1) && (n < 5) && (~~(n / 10) !== 1); - } - - function translate(number, withoutSuffix, key, isFuture) { - var result = number + ' '; - switch (key) { - case 's': // a few seconds / in a few seconds / a few seconds ago - return (withoutSuffix || isFuture) ? 'pár sekund' : 'pár sekundami'; - case 'm': // a minute / in a minute / a minute ago - return withoutSuffix ? 'minuta' : (isFuture ? 'minutu' : 'minutou'); - case 'mm': // 9 minutes / in 9 minutes / 9 minutes ago - if (withoutSuffix || isFuture) { - return result + (plural(number) ? 'minuty' : 'minut'); - } else { - return result + 'minutami'; - } - break; - case 'h': // an hour / in an hour / an hour ago - return withoutSuffix ? 'hodina' : (isFuture ? 'hodinu' : 'hodinou'); - case 'hh': // 9 hours / in 9 hours / 9 hours ago - if (withoutSuffix || isFuture) { - return result + (plural(number) ? 'hodiny' : 'hodin'); - } else { - return result + 'hodinami'; - } - break; - case 'd': // a day / in a day / a day ago - return (withoutSuffix || isFuture) ? 'den' : 'dnem'; - case 'dd': // 9 days / in 9 days / 9 days ago - if (withoutSuffix || isFuture) { - return result + (plural(number) ? 'dny' : 'dní'); - } else { - return result + 'dny'; - } - break; - case 'M': // a month / in a month / a month ago - return (withoutSuffix || isFuture) ? 'měsíc' : 'měsícem'; - case 'MM': // 9 months / in 9 months / 9 months ago - if (withoutSuffix || isFuture) { - return result + (plural(number) ? 'měsíce' : 'měsíců'); - } else { - return result + 'měsíci'; - } - break; - case 'y': // a year / in a year / a year ago - return (withoutSuffix || isFuture) ? 'rok' : 'rokem'; - case 'yy': // 9 years / in 9 years / 9 years ago - if (withoutSuffix || isFuture) { - return result + (plural(number) ? 'roky' : 'let'); - } else { - return result + 'lety'; - } - break; - } - } - - return moment.defineLocale('cs', { - months : months, - monthsShort : monthsShort, - monthsParse : (function (months, monthsShort) { - var i, _monthsParse = []; - for (i = 0; i < 12; i++) { - // use custom parser to solve problem with July (červenec) - _monthsParse[i] = new RegExp('^' + months[i] + '$|^' + monthsShort[i] + '$', 'i'); - } - return _monthsParse; - }(months, monthsShort)), - weekdays : 'neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota'.split('_'), - weekdaysShort : 'ne_po_út_st_čt_pá_so'.split('_'), - weekdaysMin : 'ne_po_út_st_čt_pá_so'.split('_'), - longDateFormat : { - LT: 'H:mm', - L : 'DD. MM. YYYY', - LL : 'D. MMMM YYYY', - LLL : 'D. MMMM YYYY LT', - LLLL : 'dddd D. MMMM YYYY LT' - }, - calendar : { - sameDay: '[dnes v] LT', - nextDay: '[zítra v] LT', - nextWeek: function () { - switch (this.day()) { - case 0: - return '[v neděli v] LT'; - case 1: - case 2: - return '[v] dddd [v] LT'; - case 3: - return '[ve středu v] LT'; - case 4: - return '[ve čtvrtek v] LT'; - case 5: - return '[v pátek v] LT'; - case 6: - return '[v sobotu v] LT'; - } - }, - lastDay: '[včera v] LT', - lastWeek: function () { - switch (this.day()) { - case 0: - return '[minulou neděli v] LT'; - case 1: - case 2: - return '[minulé] dddd [v] LT'; - case 3: - return '[minulou středu v] LT'; - case 4: - case 5: - return '[minulý] dddd [v] LT'; - case 6: - return '[minulou sobotu v] LT'; - } - }, - sameElse: 'L' - }, - relativeTime : { - future : 'za %s', - past : 'před %s', - s : translate, - m : translate, - mm : translate, - h : translate, - hh : translate, - d : translate, - dd : translate, - M : translate, - MM : translate, - y : translate, - yy : translate - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : chuvash (cv) -// author : Anatoly Mironov : https://github.com/mirontoli - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('cv', { - months : 'кăрлач_нарăс_пуш_ака_май_çĕртме_утă_çурла_авăн_юпа_чӳк_раштав'.split('_'), - monthsShort : 'кăр_нар_пуш_ака_май_çĕр_утă_çур_ав_юпа_чӳк_раш'.split('_'), - weekdays : 'вырсарникун_тунтикун_ытларикун_юнкун_кĕçнерникун_эрнекун_шăматкун'.split('_'), - weekdaysShort : 'выр_тун_ытл_юн_кĕç_эрн_шăм'.split('_'), - weekdaysMin : 'вр_тн_ыт_юн_кç_эр_шм'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD-MM-YYYY', - LL : 'YYYY [çулхи] MMMM [уйăхĕн] D[-мĕшĕ]', - LLL : 'YYYY [çулхи] MMMM [уйăхĕн] D[-мĕшĕ], LT', - LLLL : 'dddd, YYYY [çулхи] MMMM [уйăхĕн] D[-мĕшĕ], LT' - }, - calendar : { - sameDay: '[Паян] LT [сехетре]', - nextDay: '[Ыран] LT [сехетре]', - lastDay: '[Ĕнер] LT [сехетре]', - nextWeek: '[Çитес] dddd LT [сехетре]', - lastWeek: '[Иртнĕ] dddd LT [сехетре]', - sameElse: 'L' - }, - relativeTime : { - future : function (output) { - var affix = /сехет$/i.exec(output) ? 'рен' : /çул$/i.exec(output) ? 'тан' : 'ран'; - return output + affix; - }, - past : '%s каялла', - s : 'пĕр-ик çеккунт', - m : 'пĕр минут', - mm : '%d минут', - h : 'пĕр сехет', - hh : '%d сехет', - d : 'пĕр кун', - dd : '%d кун', - M : 'пĕр уйăх', - MM : '%d уйăх', - y : 'пĕр çул', - yy : '%d çул' - }, - ordinal : '%d-мĕш', - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Welsh (cy) -// author : Robert Allen - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('cy', { - months: 'Ionawr_Chwefror_Mawrth_Ebrill_Mai_Mehefin_Gorffennaf_Awst_Medi_Hydref_Tachwedd_Rhagfyr'.split('_'), - monthsShort: 'Ion_Chwe_Maw_Ebr_Mai_Meh_Gor_Aws_Med_Hyd_Tach_Rhag'.split('_'), - weekdays: 'Dydd Sul_Dydd Llun_Dydd Mawrth_Dydd Mercher_Dydd Iau_Dydd Gwener_Dydd Sadwrn'.split('_'), - weekdaysShort: 'Sul_Llun_Maw_Mer_Iau_Gwe_Sad'.split('_'), - weekdaysMin: 'Su_Ll_Ma_Me_Ia_Gw_Sa'.split('_'), - // time formats are the same as en-gb - longDateFormat: { - LT: 'HH:mm', - L: 'DD/MM/YYYY', - LL: 'D MMMM YYYY', - LLL: 'D MMMM YYYY LT', - LLLL: 'dddd, D MMMM YYYY LT' - }, - calendar: { - sameDay: '[Heddiw am] LT', - nextDay: '[Yfory am] LT', - nextWeek: 'dddd [am] LT', - lastDay: '[Ddoe am] LT', - lastWeek: 'dddd [diwethaf am] LT', - sameElse: 'L' - }, - relativeTime: { - future: 'mewn %s', - past: '%s yn ôl', - s: 'ychydig eiliadau', - m: 'munud', - mm: '%d munud', - h: 'awr', - hh: '%d awr', - d: 'diwrnod', - dd: '%d diwrnod', - M: 'mis', - MM: '%d mis', - y: 'blwyddyn', - yy: '%d flynedd' - }, - // traditional ordinal numbers above 31 are not commonly used in colloquial Welsh - ordinal: function (number) { - var b = number, - output = '', - lookup = [ - '', 'af', 'il', 'ydd', 'ydd', 'ed', 'ed', 'ed', 'fed', 'fed', 'fed', // 1af to 10fed - 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'fed' // 11eg to 20fed - ]; - - if (b > 20) { - if (b === 40 || b === 50 || b === 60 || b === 80 || b === 100) { - output = 'fed'; // not 30ain, 70ain or 90ain - } else { - output = 'ain'; - } - } else if (b > 0) { - output = lookup[b]; - } - - return number + output; - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : danish (da) -// author : Ulrik Nielsen : https://github.com/mrbase - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('da', { - months : 'januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december'.split('_'), - monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'), - weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'), - weekdaysShort : 'søn_man_tir_ons_tor_fre_lør'.split('_'), - weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D. MMMM YYYY', - LLL : 'D. MMMM YYYY LT', - LLLL : 'dddd [d.] D. MMMM YYYY LT' - }, - calendar : { - sameDay : '[I dag kl.] LT', - nextDay : '[I morgen kl.] LT', - nextWeek : 'dddd [kl.] LT', - lastDay : '[I går kl.] LT', - lastWeek : '[sidste] dddd [kl] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'om %s', - past : '%s siden', - s : 'få sekunder', - m : 'et minut', - mm : '%d minutter', - h : 'en time', - hh : '%d timer', - d : 'en dag', - dd : '%d dage', - M : 'en måned', - MM : '%d måneder', - y : 'et år', - yy : '%d år' - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : austrian german (de-at) -// author : lluchs : https://github.com/lluchs -// author: Menelion Elensúle: https://github.com/Oire -// author : Martin Groller : https://github.com/MadMG - -(function (factory) { - factory(moment); -}(function (moment) { - function processRelativeTime(number, withoutSuffix, key, isFuture) { - var format = { - 'm': ['eine Minute', 'einer Minute'], - 'h': ['eine Stunde', 'einer Stunde'], - 'd': ['ein Tag', 'einem Tag'], - 'dd': [number + ' Tage', number + ' Tagen'], - 'M': ['ein Monat', 'einem Monat'], - 'MM': [number + ' Monate', number + ' Monaten'], - 'y': ['ein Jahr', 'einem Jahr'], - 'yy': [number + ' Jahre', number + ' Jahren'] - }; - return withoutSuffix ? format[key][0] : format[key][1]; - } - - return moment.defineLocale('de-at', { - months : 'Jänner_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'), - monthsShort : 'Jän._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.'.split('_'), - weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'), - weekdaysShort : 'So._Mo._Di._Mi._Do._Fr._Sa.'.split('_'), - weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'), - longDateFormat : { - LT: 'HH:mm [Uhr]', - L : 'DD.MM.YYYY', - LL : 'D. MMMM YYYY', - LLL : 'D. MMMM YYYY LT', - LLLL : 'dddd, D. MMMM YYYY LT' - }, - calendar : { - sameDay: '[Heute um] LT', - sameElse: 'L', - nextDay: '[Morgen um] LT', - nextWeek: 'dddd [um] LT', - lastDay: '[Gestern um] LT', - lastWeek: '[letzten] dddd [um] LT' - }, - relativeTime : { - future : 'in %s', - past : 'vor %s', - s : 'ein paar Sekunden', - m : processRelativeTime, - mm : '%d Minuten', - h : processRelativeTime, - hh : '%d Stunden', - d : processRelativeTime, - dd : processRelativeTime, - M : processRelativeTime, - MM : processRelativeTime, - y : processRelativeTime, - yy : processRelativeTime - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : german (de) -// author : lluchs : https://github.com/lluchs -// author: Menelion Elensúle: https://github.com/Oire - -(function (factory) { - factory(moment); -}(function (moment) { - function processRelativeTime(number, withoutSuffix, key, isFuture) { - var format = { - 'm': ['eine Minute', 'einer Minute'], - 'h': ['eine Stunde', 'einer Stunde'], - 'd': ['ein Tag', 'einem Tag'], - 'dd': [number + ' Tage', number + ' Tagen'], - 'M': ['ein Monat', 'einem Monat'], - 'MM': [number + ' Monate', number + ' Monaten'], - 'y': ['ein Jahr', 'einem Jahr'], - 'yy': [number + ' Jahre', number + ' Jahren'] - }; - return withoutSuffix ? format[key][0] : format[key][1]; - } - - return moment.defineLocale('de', { - months : 'Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'), - monthsShort : 'Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.'.split('_'), - weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'), - weekdaysShort : 'So._Mo._Di._Mi._Do._Fr._Sa.'.split('_'), - weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'), - longDateFormat : { - LT: 'HH:mm [Uhr]', - L : 'DD.MM.YYYY', - LL : 'D. MMMM YYYY', - LLL : 'D. MMMM YYYY LT', - LLLL : 'dddd, D. MMMM YYYY LT' - }, - calendar : { - sameDay: '[Heute um] LT', - sameElse: 'L', - nextDay: '[Morgen um] LT', - nextWeek: 'dddd [um] LT', - lastDay: '[Gestern um] LT', - lastWeek: '[letzten] dddd [um] LT' - }, - relativeTime : { - future : 'in %s', - past : 'vor %s', - s : 'ein paar Sekunden', - m : processRelativeTime, - mm : '%d Minuten', - h : processRelativeTime, - hh : '%d Stunden', - d : processRelativeTime, - dd : processRelativeTime, - M : processRelativeTime, - MM : processRelativeTime, - y : processRelativeTime, - yy : processRelativeTime - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : modern greek (el) -// author : Aggelos Karalias : https://github.com/mehiel - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('el', { - monthsNominativeEl : 'Ιανουάριος_Φεβρουάριος_Μάρτιος_Απρίλιος_Μάιος_Ιούνιος_Ιούλιος_Αύγουστος_Σεπτέμβριος_Οκτώβριος_Νοέμβριος_Δεκέμβριος'.split('_'), - monthsGenitiveEl : 'Ιανουαρίου_Φεβρουαρίου_Μαρτίου_Απριλίου_Μαΐου_Ιουνίου_Ιουλίου_Αυγούστου_Σεπτεμβρίου_Οκτωβρίου_Νοεμβρίου_Δεκεμβρίου'.split('_'), - months : function (momentToFormat, format) { - if (/D/.test(format.substring(0, format.indexOf('MMMM')))) { // if there is a day number before 'MMMM' - return this._monthsGenitiveEl[momentToFormat.month()]; - } else { - return this._monthsNominativeEl[momentToFormat.month()]; - } - }, - monthsShort : 'Ιαν_Φεβ_Μαρ_Απρ_Μαϊ_Ιουν_Ιουλ_Αυγ_Σεπ_Οκτ_Νοε_Δεκ'.split('_'), - weekdays : 'Κυριακή_Δευτέρα_Τρίτη_Τετάρτη_Πέμπτη_Παρασκευή_Σάββατο'.split('_'), - weekdaysShort : 'Κυρ_Δευ_Τρι_Τετ_Πεμ_Παρ_Σαβ'.split('_'), - weekdaysMin : 'Κυ_Δε_Τρ_Τε_Πε_Πα_Σα'.split('_'), - meridiem : function (hours, minutes, isLower) { - if (hours > 11) { - return isLower ? 'μμ' : 'ΜΜ'; - } else { - return isLower ? 'πμ' : 'ΠΜ'; - } - }, - isPM : function (input) { - return ((input + '').toLowerCase()[0] === 'μ'); - }, - meridiemParse : /[ΠΜ]\.?Μ?\.?/i, - longDateFormat : { - LT : 'h:mm A', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd, D MMMM YYYY LT' - }, - calendarEl : { - sameDay : '[Σήμερα {}] LT', - nextDay : '[Αύριο {}] LT', - nextWeek : 'dddd [{}] LT', - lastDay : '[Χθες {}] LT', - lastWeek : function () { - switch (this.day()) { - case 6: - return '[το προηγούμενο] dddd [{}] LT'; - default: - return '[την προηγούμενη] dddd [{}] LT'; - } - }, - sameElse : 'L' - }, - calendar : function (key, mom) { - var output = this._calendarEl[key], - hours = mom && mom.hours(); - - if (typeof output === 'function') { - output = output.apply(mom); - } - - return output.replace('{}', (hours % 12 === 1 ? 'στη' : 'στις')); - }, - relativeTime : { - future : 'σε %s', - past : '%s πριν', - s : 'δευτερόλεπτα', - m : 'ένα λεπτό', - mm : '%d λεπτά', - h : 'μία ώρα', - hh : '%d ώρες', - d : 'μία μέρα', - dd : '%d μέρες', - M : 'ένας μήνας', - MM : '%d μήνες', - y : 'ένας χρόνος', - yy : '%d χρόνια' - }, - ordinal : function (number) { - return number + 'η'; - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : australian english (en-au) - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('en-au', { - months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), - monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), - weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), - weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), - weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), - longDateFormat : { - LT : 'h:mm A', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd, D MMMM YYYY LT' - }, - calendar : { - sameDay : '[Today at] LT', - nextDay : '[Tomorrow at] LT', - nextWeek : 'dddd [at] LT', - lastDay : '[Yesterday at] LT', - lastWeek : '[Last] dddd [at] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'in %s', - past : '%s ago', - s : 'a few seconds', - m : 'a minute', - mm : '%d minutes', - h : 'an hour', - hh : '%d hours', - d : 'a day', - dd : '%d days', - M : 'a month', - MM : '%d months', - y : 'a year', - yy : '%d years' - }, - ordinal : function (number) { - var b = number % 10, - output = (~~(number % 100 / 10) === 1) ? 'th' : - (b === 1) ? 'st' : - (b === 2) ? 'nd' : - (b === 3) ? 'rd' : 'th'; - return number + output; - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : canadian english (en-ca) -// author : Jonathan Abourbih : https://github.com/jonbca - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('en-ca', { - months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), - monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), - weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), - weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), - weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), - longDateFormat : { - LT : 'h:mm A', - L : 'YYYY-MM-DD', - LL : 'D MMMM, YYYY', - LLL : 'D MMMM, YYYY LT', - LLLL : 'dddd, D MMMM, YYYY LT' - }, - calendar : { - sameDay : '[Today at] LT', - nextDay : '[Tomorrow at] LT', - nextWeek : 'dddd [at] LT', - lastDay : '[Yesterday at] LT', - lastWeek : '[Last] dddd [at] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'in %s', - past : '%s ago', - s : 'a few seconds', - m : 'a minute', - mm : '%d minutes', - h : 'an hour', - hh : '%d hours', - d : 'a day', - dd : '%d days', - M : 'a month', - MM : '%d months', - y : 'a year', - yy : '%d years' - }, - ordinal : function (number) { - var b = number % 10, - output = (~~(number % 100 / 10) === 1) ? 'th' : - (b === 1) ? 'st' : - (b === 2) ? 'nd' : - (b === 3) ? 'rd' : 'th'; - return number + output; - } - }); -})); -// moment.js locale configuration -// locale : great britain english (en-gb) -// author : Chris Gedrim : https://github.com/chrisgedrim - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('en-gb', { - months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), - monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), - weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), - weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), - weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd, D MMMM YYYY LT' - }, - calendar : { - sameDay : '[Today at] LT', - nextDay : '[Tomorrow at] LT', - nextWeek : 'dddd [at] LT', - lastDay : '[Yesterday at] LT', - lastWeek : '[Last] dddd [at] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'in %s', - past : '%s ago', - s : 'a few seconds', - m : 'a minute', - mm : '%d minutes', - h : 'an hour', - hh : '%d hours', - d : 'a day', - dd : '%d days', - M : 'a month', - MM : '%d months', - y : 'a year', - yy : '%d years' - }, - ordinal : function (number) { - var b = number % 10, - output = (~~(number % 100 / 10) === 1) ? 'th' : - (b === 1) ? 'st' : - (b === 2) ? 'nd' : - (b === 3) ? 'rd' : 'th'; - return number + output; - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : esperanto (eo) -// author : Colin Dean : https://github.com/colindean -// komento: Mi estas malcerta se mi korekte traktis akuzativojn en tiu traduko. -// Se ne, bonvolu korekti kaj avizi min por ke mi povas lerni! - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('eo', { - months : 'januaro_februaro_marto_aprilo_majo_junio_julio_aŭgusto_septembro_oktobro_novembro_decembro'.split('_'), - monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aŭg_sep_okt_nov_dec'.split('_'), - weekdays : 'Dimanĉo_Lundo_Mardo_Merkredo_Ĵaŭdo_Vendredo_Sabato'.split('_'), - weekdaysShort : 'Dim_Lun_Mard_Merk_Ĵaŭ_Ven_Sab'.split('_'), - weekdaysMin : 'Di_Lu_Ma_Me_Ĵa_Ve_Sa'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'YYYY-MM-DD', - LL : 'D[-an de] MMMM, YYYY', - LLL : 'D[-an de] MMMM, YYYY LT', - LLLL : 'dddd, [la] D[-an de] MMMM, YYYY LT' - }, - meridiem : function (hours, minutes, isLower) { - if (hours > 11) { - return isLower ? 'p.t.m.' : 'P.T.M.'; - } else { - return isLower ? 'a.t.m.' : 'A.T.M.'; - } - }, - calendar : { - sameDay : '[Hodiaŭ je] LT', - nextDay : '[Morgaŭ je] LT', - nextWeek : 'dddd [je] LT', - lastDay : '[Hieraŭ je] LT', - lastWeek : '[pasinta] dddd [je] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'je %s', - past : 'antaŭ %s', - s : 'sekundoj', - m : 'minuto', - mm : '%d minutoj', - h : 'horo', - hh : '%d horoj', - d : 'tago',//ne 'diurno', ĉar estas uzita por proksimumo - dd : '%d tagoj', - M : 'monato', - MM : '%d monatoj', - y : 'jaro', - yy : '%d jaroj' - }, - ordinal : '%da', - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : spanish (es) -// author : Julio Napurí : https://github.com/julionc - -(function (factory) { - factory(moment); -}(function (moment) { - var monthsShortDot = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_'), - monthsShort = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_'); - - return moment.defineLocale('es', { - months : 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'), - monthsShort : function (m, format) { - if (/-MMM-/.test(format)) { - return monthsShort[m.month()]; - } else { - return monthsShortDot[m.month()]; - } - }, - weekdays : 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'), - weekdaysShort : 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'), - weekdaysMin : 'Do_Lu_Ma_Mi_Ju_Vi_Sá'.split('_'), - longDateFormat : { - LT : 'H:mm', - L : 'DD/MM/YYYY', - LL : 'D [de] MMMM [de] YYYY', - LLL : 'D [de] MMMM [de] YYYY LT', - LLLL : 'dddd, D [de] MMMM [de] YYYY LT' - }, - calendar : { - sameDay : function () { - return '[hoy a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; - }, - nextDay : function () { - return '[mañana a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; - }, - nextWeek : function () { - return 'dddd [a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; - }, - lastDay : function () { - return '[ayer a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; - }, - lastWeek : function () { - return '[el] dddd [pasado a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; - }, - sameElse : 'L' - }, - relativeTime : { - future : 'en %s', - past : 'hace %s', - s : 'unos segundos', - m : 'un minuto', - mm : '%d minutos', - h : 'una hora', - hh : '%d horas', - d : 'un día', - dd : '%d días', - M : 'un mes', - MM : '%d meses', - y : 'un año', - yy : '%d años' - }, - ordinal : '%dº', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : estonian (et) -// author : Henry Kehlmann : https://github.com/madhenry -// improvements : Illimar Tambek : https://github.com/ragulka - -(function (factory) { - factory(moment); -}(function (moment) { - function processRelativeTime(number, withoutSuffix, key, isFuture) { - var format = { - 's' : ['mõne sekundi', 'mõni sekund', 'paar sekundit'], - 'm' : ['ühe minuti', 'üks minut'], - 'mm': [number + ' minuti', number + ' minutit'], - 'h' : ['ühe tunni', 'tund aega', 'üks tund'], - 'hh': [number + ' tunni', number + ' tundi'], - 'd' : ['ühe päeva', 'üks päev'], - 'M' : ['kuu aja', 'kuu aega', 'üks kuu'], - 'MM': [number + ' kuu', number + ' kuud'], - 'y' : ['ühe aasta', 'aasta', 'üks aasta'], - 'yy': [number + ' aasta', number + ' aastat'] - }; - if (withoutSuffix) { - return format[key][2] ? format[key][2] : format[key][1]; - } - return isFuture ? format[key][0] : format[key][1]; - } - - return moment.defineLocale('et', { - months : 'jaanuar_veebruar_märts_aprill_mai_juuni_juuli_august_september_oktoober_november_detsember'.split('_'), - monthsShort : 'jaan_veebr_märts_apr_mai_juuni_juuli_aug_sept_okt_nov_dets'.split('_'), - weekdays : 'pühapäev_esmaspäev_teisipäev_kolmapäev_neljapäev_reede_laupäev'.split('_'), - weekdaysShort : 'P_E_T_K_N_R_L'.split('_'), - weekdaysMin : 'P_E_T_K_N_R_L'.split('_'), - longDateFormat : { - LT : 'H:mm', - L : 'DD.MM.YYYY', - LL : 'D. MMMM YYYY', - LLL : 'D. MMMM YYYY LT', - LLLL : 'dddd, D. MMMM YYYY LT' - }, - calendar : { - sameDay : '[Täna,] LT', - nextDay : '[Homme,] LT', - nextWeek : '[Järgmine] dddd LT', - lastDay : '[Eile,] LT', - lastWeek : '[Eelmine] dddd LT', - sameElse : 'L' - }, - relativeTime : { - future : '%s pärast', - past : '%s tagasi', - s : processRelativeTime, - m : processRelativeTime, - mm : processRelativeTime, - h : processRelativeTime, - hh : processRelativeTime, - d : processRelativeTime, - dd : '%d päeva', - M : processRelativeTime, - MM : processRelativeTime, - y : processRelativeTime, - yy : processRelativeTime - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : euskara (eu) -// author : Eneko Illarramendi : https://github.com/eillarra - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('eu', { - months : 'urtarrila_otsaila_martxoa_apirila_maiatza_ekaina_uztaila_abuztua_iraila_urria_azaroa_abendua'.split('_'), - monthsShort : 'urt._ots._mar._api._mai._eka._uzt._abu._ira._urr._aza._abe.'.split('_'), - weekdays : 'igandea_astelehena_asteartea_asteazkena_osteguna_ostirala_larunbata'.split('_'), - weekdaysShort : 'ig._al._ar._az._og._ol._lr.'.split('_'), - weekdaysMin : 'ig_al_ar_az_og_ol_lr'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'YYYY-MM-DD', - LL : 'YYYY[ko] MMMM[ren] D[a]', - LLL : 'YYYY[ko] MMMM[ren] D[a] LT', - LLLL : 'dddd, YYYY[ko] MMMM[ren] D[a] LT', - l : 'YYYY-M-D', - ll : 'YYYY[ko] MMM D[a]', - lll : 'YYYY[ko] MMM D[a] LT', - llll : 'ddd, YYYY[ko] MMM D[a] LT' - }, - calendar : { - sameDay : '[gaur] LT[etan]', - nextDay : '[bihar] LT[etan]', - nextWeek : 'dddd LT[etan]', - lastDay : '[atzo] LT[etan]', - lastWeek : '[aurreko] dddd LT[etan]', - sameElse : 'L' - }, - relativeTime : { - future : '%s barru', - past : 'duela %s', - s : 'segundo batzuk', - m : 'minutu bat', - mm : '%d minutu', - h : 'ordu bat', - hh : '%d ordu', - d : 'egun bat', - dd : '%d egun', - M : 'hilabete bat', - MM : '%d hilabete', - y : 'urte bat', - yy : '%d urte' - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Persian (fa) -// author : Ebrahim Byagowi : https://github.com/ebraminio - -(function (factory) { - factory(moment); -}(function (moment) { - var symbolMap = { - '1': '۱', - '2': '۲', - '3': '۳', - '4': '۴', - '5': '۵', - '6': '۶', - '7': '۷', - '8': '۸', - '9': '۹', - '0': '۰' - }, numberMap = { - '۱': '1', - '۲': '2', - '۳': '3', - '۴': '4', - '۵': '5', - '۶': '6', - '۷': '7', - '۸': '8', - '۹': '9', - '۰': '0' - }; - - return moment.defineLocale('fa', { - months : 'ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر'.split('_'), - monthsShort : 'ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر'.split('_'), - weekdays : 'یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_جمعه_شنبه'.split('_'), - weekdaysShort : 'یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_جمعه_شنبه'.split('_'), - weekdaysMin : 'ی_د_س_چ_پ_ج_ش'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd, D MMMM YYYY LT' - }, - meridiem : function (hour, minute, isLower) { - if (hour < 12) { - return 'قبل از ظهر'; - } else { - return 'بعد از ظهر'; - } - }, - calendar : { - sameDay : '[امروز ساعت] LT', - nextDay : '[فردا ساعت] LT', - nextWeek : 'dddd [ساعت] LT', - lastDay : '[دیروز ساعت] LT', - lastWeek : 'dddd [پیش] [ساعت] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'در %s', - past : '%s پیش', - s : 'چندین ثانیه', - m : 'یک دقیقه', - mm : '%d دقیقه', - h : 'یک ساعت', - hh : '%d ساعت', - d : 'یک روز', - dd : '%d روز', - M : 'یک ماه', - MM : '%d ماه', - y : 'یک سال', - yy : '%d سال' - }, - preparse: function (string) { - return string.replace(/[۰-۹]/g, function (match) { - return numberMap[match]; - }).replace(/،/g, ','); - }, - postformat: function (string) { - return string.replace(/\d/g, function (match) { - return symbolMap[match]; - }).replace(/,/g, '،'); - }, - ordinal : '%dم', - week : { - dow : 6, // Saturday is the first day of the week. - doy : 12 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : finnish (fi) -// author : Tarmo Aidantausta : https://github.com/bleadof - -(function (factory) { - factory(moment); -}(function (moment) { - var numbersPast = 'nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän'.split(' '), - numbersFuture = [ - 'nolla', 'yhden', 'kahden', 'kolmen', 'neljän', 'viiden', 'kuuden', - numbersPast[7], numbersPast[8], numbersPast[9] - ]; - - function translate(number, withoutSuffix, key, isFuture) { - var result = ''; - switch (key) { - case 's': - return isFuture ? 'muutaman sekunnin' : 'muutama sekunti'; - case 'm': - return isFuture ? 'minuutin' : 'minuutti'; - case 'mm': - result = isFuture ? 'minuutin' : 'minuuttia'; - break; - case 'h': - return isFuture ? 'tunnin' : 'tunti'; - case 'hh': - result = isFuture ? 'tunnin' : 'tuntia'; - break; - case 'd': - return isFuture ? 'päivän' : 'päivä'; - case 'dd': - result = isFuture ? 'päivän' : 'päivää'; - break; - case 'M': - return isFuture ? 'kuukauden' : 'kuukausi'; - case 'MM': - result = isFuture ? 'kuukauden' : 'kuukautta'; - break; - case 'y': - return isFuture ? 'vuoden' : 'vuosi'; - case 'yy': - result = isFuture ? 'vuoden' : 'vuotta'; - break; - } - result = verbalNumber(number, isFuture) + ' ' + result; - return result; - } - - function verbalNumber(number, isFuture) { - return number < 10 ? (isFuture ? numbersFuture[number] : numbersPast[number]) : number; - } - - return moment.defineLocale('fi', { - months : 'tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu'.split('_'), - monthsShort : 'tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu'.split('_'), - weekdays : 'sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai'.split('_'), - weekdaysShort : 'su_ma_ti_ke_to_pe_la'.split('_'), - weekdaysMin : 'su_ma_ti_ke_to_pe_la'.split('_'), - longDateFormat : { - LT : 'HH.mm', - L : 'DD.MM.YYYY', - LL : 'Do MMMM[ta] YYYY', - LLL : 'Do MMMM[ta] YYYY, [klo] LT', - LLLL : 'dddd, Do MMMM[ta] YYYY, [klo] LT', - l : 'D.M.YYYY', - ll : 'Do MMM YYYY', - lll : 'Do MMM YYYY, [klo] LT', - llll : 'ddd, Do MMM YYYY, [klo] LT' - }, - calendar : { - sameDay : '[tänään] [klo] LT', - nextDay : '[huomenna] [klo] LT', - nextWeek : 'dddd [klo] LT', - lastDay : '[eilen] [klo] LT', - lastWeek : '[viime] dddd[na] [klo] LT', - sameElse : 'L' - }, - relativeTime : { - future : '%s päästä', - past : '%s sitten', - s : translate, - m : translate, - mm : translate, - h : translate, - hh : translate, - d : translate, - dd : translate, - M : translate, - MM : translate, - y : translate, - yy : translate - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : faroese (fo) -// author : Ragnar Johannesen : https://github.com/ragnar123 - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('fo', { - months : 'januar_februar_mars_apríl_mai_juni_juli_august_september_oktober_november_desember'.split('_'), - monthsShort : 'jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des'.split('_'), - weekdays : 'sunnudagur_mánadagur_týsdagur_mikudagur_hósdagur_fríggjadagur_leygardagur'.split('_'), - weekdaysShort : 'sun_mán_týs_mik_hós_frí_ley'.split('_'), - weekdaysMin : 'su_má_tý_mi_hó_fr_le'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd D. MMMM, YYYY LT' - }, - calendar : { - sameDay : '[Í dag kl.] LT', - nextDay : '[Í morgin kl.] LT', - nextWeek : 'dddd [kl.] LT', - lastDay : '[Í gjár kl.] LT', - lastWeek : '[síðstu] dddd [kl] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'um %s', - past : '%s síðani', - s : 'fá sekund', - m : 'ein minutt', - mm : '%d minuttir', - h : 'ein tími', - hh : '%d tímar', - d : 'ein dagur', - dd : '%d dagar', - M : 'ein mánaði', - MM : '%d mánaðir', - y : 'eitt ár', - yy : '%d ár' - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : canadian french (fr-ca) -// author : Jonathan Abourbih : https://github.com/jonbca - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('fr-ca', { - months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'), - monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'), - weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'), - weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'), - weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'YYYY-MM-DD', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd D MMMM YYYY LT' - }, - calendar : { - sameDay: '[Aujourd\'hui à] LT', - nextDay: '[Demain à] LT', - nextWeek: 'dddd [à] LT', - lastDay: '[Hier à] LT', - lastWeek: 'dddd [dernier à] LT', - sameElse: 'L' - }, - relativeTime : { - future : 'dans %s', - past : 'il y a %s', - s : 'quelques secondes', - m : 'une minute', - mm : '%d minutes', - h : 'une heure', - hh : '%d heures', - d : 'un jour', - dd : '%d jours', - M : 'un mois', - MM : '%d mois', - y : 'un an', - yy : '%d ans' - }, - ordinal : function (number) { - return number + (number === 1 ? 'er' : ''); - } - }); -})); -// moment.js locale configuration -// locale : french (fr) -// author : John Fischer : https://github.com/jfroffice - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('fr', { - months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'), - monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'), - weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'), - weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'), - weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd D MMMM YYYY LT' - }, - calendar : { - sameDay: '[Aujourd\'hui à] LT', - nextDay: '[Demain à] LT', - nextWeek: 'dddd [à] LT', - lastDay: '[Hier à] LT', - lastWeek: 'dddd [dernier à] LT', - sameElse: 'L' - }, - relativeTime : { - future : 'dans %s', - past : 'il y a %s', - s : 'quelques secondes', - m : 'une minute', - mm : '%d minutes', - h : 'une heure', - hh : '%d heures', - d : 'un jour', - dd : '%d jours', - M : 'un mois', - MM : '%d mois', - y : 'un an', - yy : '%d ans' - }, - ordinal : function (number) { - return number + (number === 1 ? 'er' : ''); - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : galician (gl) -// author : Juan G. Hurtado : https://github.com/juanghurtado - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('gl', { - months : 'Xaneiro_Febreiro_Marzo_Abril_Maio_Xuño_Xullo_Agosto_Setembro_Outubro_Novembro_Decembro'.split('_'), - monthsShort : 'Xan._Feb._Mar._Abr._Mai._Xuñ._Xul._Ago._Set._Out._Nov._Dec.'.split('_'), - weekdays : 'Domingo_Luns_Martes_Mércores_Xoves_Venres_Sábado'.split('_'), - weekdaysShort : 'Dom._Lun._Mar._Mér._Xov._Ven._Sáb.'.split('_'), - weekdaysMin : 'Do_Lu_Ma_Mé_Xo_Ve_Sá'.split('_'), - longDateFormat : { - LT : 'H:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd D MMMM YYYY LT' - }, - calendar : { - sameDay : function () { - return '[hoxe ' + ((this.hours() !== 1) ? 'ás' : 'á') + '] LT'; - }, - nextDay : function () { - return '[mañá ' + ((this.hours() !== 1) ? 'ás' : 'á') + '] LT'; - }, - nextWeek : function () { - return 'dddd [' + ((this.hours() !== 1) ? 'ás' : 'a') + '] LT'; - }, - lastDay : function () { - return '[onte ' + ((this.hours() !== 1) ? 'á' : 'a') + '] LT'; - }, - lastWeek : function () { - return '[o] dddd [pasado ' + ((this.hours() !== 1) ? 'ás' : 'a') + '] LT'; - }, - sameElse : 'L' - }, - relativeTime : { - future : function (str) { - if (str === 'uns segundos') { - return 'nuns segundos'; - } - return 'en ' + str; - }, - past : 'hai %s', - s : 'uns segundos', - m : 'un minuto', - mm : '%d minutos', - h : 'unha hora', - hh : '%d horas', - d : 'un día', - dd : '%d días', - M : 'un mes', - MM : '%d meses', - y : 'un ano', - yy : '%d anos' - }, - ordinal : '%dº', - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Hebrew (he) -// author : Tomer Cohen : https://github.com/tomer -// author : Moshe Simantov : https://github.com/DevelopmentIL -// author : Tal Ater : https://github.com/TalAter - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('he', { - months : 'ינואר_פברואר_מרץ_אפריל_מאי_יוני_יולי_אוגוסט_ספטמבר_אוקטובר_נובמבר_דצמבר'.split('_'), - monthsShort : 'ינו׳_פבר׳_מרץ_אפר׳_מאי_יוני_יולי_אוג׳_ספט׳_אוק׳_נוב׳_דצמ׳'.split('_'), - weekdays : 'ראשון_שני_שלישי_רביעי_חמישי_שישי_שבת'.split('_'), - weekdaysShort : 'א׳_ב׳_ג׳_ד׳_ה׳_ו׳_ש׳'.split('_'), - weekdaysMin : 'א_ב_ג_ד_ה_ו_ש'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D [ב]MMMM YYYY', - LLL : 'D [ב]MMMM YYYY LT', - LLLL : 'dddd, D [ב]MMMM YYYY LT', - l : 'D/M/YYYY', - ll : 'D MMM YYYY', - lll : 'D MMM YYYY LT', - llll : 'ddd, D MMM YYYY LT' - }, - calendar : { - sameDay : '[היום ב־]LT', - nextDay : '[מחר ב־]LT', - nextWeek : 'dddd [בשעה] LT', - lastDay : '[אתמול ב־]LT', - lastWeek : '[ביום] dddd [האחרון בשעה] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'בעוד %s', - past : 'לפני %s', - s : 'מספר שניות', - m : 'דקה', - mm : '%d דקות', - h : 'שעה', - hh : function (number) { - if (number === 2) { - return 'שעתיים'; - } - return number + ' שעות'; - }, - d : 'יום', - dd : function (number) { - if (number === 2) { - return 'יומיים'; - } - return number + ' ימים'; - }, - M : 'חודש', - MM : function (number) { - if (number === 2) { - return 'חודשיים'; - } - return number + ' חודשים'; - }, - y : 'שנה', - yy : function (number) { - if (number === 2) { - return 'שנתיים'; - } - return number + ' שנים'; - } - } - }); -})); -// moment.js locale configuration -// locale : hindi (hi) -// author : Mayank Singhal : https://github.com/mayanksinghal - -(function (factory) { - factory(moment); -}(function (moment) { - var symbolMap = { - '1': '१', - '2': '२', - '3': '३', - '4': '४', - '5': '५', - '6': '६', - '7': '७', - '8': '८', - '9': '९', - '0': '०' - }, - numberMap = { - '१': '1', - '२': '2', - '३': '3', - '४': '4', - '५': '5', - '६': '6', - '७': '7', - '८': '8', - '९': '9', - '०': '0' - }; - - return moment.defineLocale('hi', { - months : 'जनवरी_फ़रवरी_मार्च_अप्रैल_मई_जून_जुलाई_अगस्त_सितम्बर_अक्टूबर_नवम्बर_दिसम्बर'.split('_'), - monthsShort : 'जन._फ़र._मार्च_अप्रै._मई_जून_जुल._अग._सित._अक्टू._नव._दिस.'.split('_'), - weekdays : 'रविवार_सोमवार_मंगलवार_बुधवार_गुरूवार_शुक्रवार_शनिवार'.split('_'), - weekdaysShort : 'रवि_सोम_मंगल_बुध_गुरू_शुक्र_शनि'.split('_'), - weekdaysMin : 'र_सो_मं_बु_गु_शु_श'.split('_'), - longDateFormat : { - LT : 'A h:mm बजे', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY, LT', - LLLL : 'dddd, D MMMM YYYY, LT' - }, - calendar : { - sameDay : '[आज] LT', - nextDay : '[कल] LT', - nextWeek : 'dddd, LT', - lastDay : '[कल] LT', - lastWeek : '[पिछले] dddd, LT', - sameElse : 'L' - }, - relativeTime : { - future : '%s में', - past : '%s पहले', - s : 'कुछ ही क्षण', - m : 'एक मिनट', - mm : '%d मिनट', - h : 'एक घंटा', - hh : '%d घंटे', - d : 'एक दिन', - dd : '%d दिन', - M : 'एक महीने', - MM : '%d महीने', - y : 'एक वर्ष', - yy : '%d वर्ष' - }, - preparse: function (string) { - return string.replace(/[१२३४५६७८९०]/g, function (match) { - return numberMap[match]; - }); - }, - postformat: function (string) { - return string.replace(/\d/g, function (match) { - return symbolMap[match]; - }); - }, - // Hindi notation for meridiems are quite fuzzy in practice. While there exists - // a rigid notion of a 'Pahar' it is not used as rigidly in modern Hindi. - meridiem : function (hour, minute, isLower) { - if (hour < 4) { - return 'रात'; - } else if (hour < 10) { - return 'सुबह'; - } else if (hour < 17) { - return 'दोपहर'; - } else if (hour < 20) { - return 'शाम'; - } else { - return 'रात'; - } - }, - week : { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : hrvatski (hr) -// author : Bojan Marković : https://github.com/bmarkovic - -// based on (sl) translation by Robert Sedovšek - -(function (factory) { - factory(moment); -}(function (moment) { - function translate(number, withoutSuffix, key) { - var result = number + ' '; - switch (key) { - case 'm': - return withoutSuffix ? 'jedna minuta' : 'jedne minute'; - case 'mm': - if (number === 1) { - result += 'minuta'; - } else if (number === 2 || number === 3 || number === 4) { - result += 'minute'; - } else { - result += 'minuta'; - } - return result; - case 'h': - return withoutSuffix ? 'jedan sat' : 'jednog sata'; - case 'hh': - if (number === 1) { - result += 'sat'; - } else if (number === 2 || number === 3 || number === 4) { - result += 'sata'; - } else { - result += 'sati'; - } - return result; - case 'dd': - if (number === 1) { - result += 'dan'; - } else { - result += 'dana'; - } - return result; - case 'MM': - if (number === 1) { - result += 'mjesec'; - } else if (number === 2 || number === 3 || number === 4) { - result += 'mjeseca'; - } else { - result += 'mjeseci'; - } - return result; - case 'yy': - if (number === 1) { - result += 'godina'; - } else if (number === 2 || number === 3 || number === 4) { - result += 'godine'; - } else { - result += 'godina'; - } - return result; - } - } - - return moment.defineLocale('hr', { - months : 'sječanj_veljača_ožujak_travanj_svibanj_lipanj_srpanj_kolovoz_rujan_listopad_studeni_prosinac'.split('_'), - monthsShort : 'sje._vel._ožu._tra._svi._lip._srp._kol._ruj._lis._stu._pro.'.split('_'), - weekdays : 'nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota'.split('_'), - weekdaysShort : 'ned._pon._uto._sri._čet._pet._sub.'.split('_'), - weekdaysMin : 'ne_po_ut_sr_če_pe_su'.split('_'), - longDateFormat : { - LT : 'H:mm', - L : 'DD. MM. YYYY', - LL : 'D. MMMM YYYY', - LLL : 'D. MMMM YYYY LT', - LLLL : 'dddd, D. MMMM YYYY LT' - }, - calendar : { - sameDay : '[danas u] LT', - nextDay : '[sutra u] LT', - - nextWeek : function () { - switch (this.day()) { - case 0: - return '[u] [nedjelju] [u] LT'; - case 3: - return '[u] [srijedu] [u] LT'; - case 6: - return '[u] [subotu] [u] LT'; - case 1: - case 2: - case 4: - case 5: - return '[u] dddd [u] LT'; - } - }, - lastDay : '[jučer u] LT', - lastWeek : function () { - switch (this.day()) { - case 0: - case 3: - return '[prošlu] dddd [u] LT'; - case 6: - return '[prošle] [subote] [u] LT'; - case 1: - case 2: - case 4: - case 5: - return '[prošli] dddd [u] LT'; - } - }, - sameElse : 'L' - }, - relativeTime : { - future : 'za %s', - past : 'prije %s', - s : 'par sekundi', - m : translate, - mm : translate, - h : translate, - hh : translate, - d : 'dan', - dd : translate, - M : 'mjesec', - MM : translate, - y : 'godinu', - yy : translate - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : hungarian (hu) -// author : Adam Brunner : https://github.com/adambrunner - -(function (factory) { - factory(moment); -}(function (moment) { - var weekEndings = 'vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton'.split(' '); - - function translate(number, withoutSuffix, key, isFuture) { - var num = number, - suffix; - - switch (key) { - case 's': - return (isFuture || withoutSuffix) ? 'néhány másodperc' : 'néhány másodperce'; - case 'm': - return 'egy' + (isFuture || withoutSuffix ? ' perc' : ' perce'); - case 'mm': - return num + (isFuture || withoutSuffix ? ' perc' : ' perce'); - case 'h': - return 'egy' + (isFuture || withoutSuffix ? ' óra' : ' órája'); - case 'hh': - return num + (isFuture || withoutSuffix ? ' óra' : ' órája'); - case 'd': - return 'egy' + (isFuture || withoutSuffix ? ' nap' : ' napja'); - case 'dd': - return num + (isFuture || withoutSuffix ? ' nap' : ' napja'); - case 'M': - return 'egy' + (isFuture || withoutSuffix ? ' hónap' : ' hónapja'); - case 'MM': - return num + (isFuture || withoutSuffix ? ' hónap' : ' hónapja'); - case 'y': - return 'egy' + (isFuture || withoutSuffix ? ' év' : ' éve'); - case 'yy': - return num + (isFuture || withoutSuffix ? ' év' : ' éve'); - } - - return ''; - } - - function week(isFuture) { - return (isFuture ? '' : '[múlt] ') + '[' + weekEndings[this.day()] + '] LT[-kor]'; - } - - return moment.defineLocale('hu', { - months : 'január_február_március_április_május_június_július_augusztus_szeptember_október_november_december'.split('_'), - monthsShort : 'jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec'.split('_'), - weekdays : 'vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat'.split('_'), - weekdaysShort : 'vas_hét_kedd_sze_csüt_pén_szo'.split('_'), - weekdaysMin : 'v_h_k_sze_cs_p_szo'.split('_'), - longDateFormat : { - LT : 'H:mm', - L : 'YYYY.MM.DD.', - LL : 'YYYY. MMMM D.', - LLL : 'YYYY. MMMM D., LT', - LLLL : 'YYYY. MMMM D., dddd LT' - }, - meridiem : function (hours, minutes, isLower) { - if (hours < 12) { - return isLower === true ? 'de' : 'DE'; - } else { - return isLower === true ? 'du' : 'DU'; - } - }, - calendar : { - sameDay : '[ma] LT[-kor]', - nextDay : '[holnap] LT[-kor]', - nextWeek : function () { - return week.call(this, true); - }, - lastDay : '[tegnap] LT[-kor]', - lastWeek : function () { - return week.call(this, false); - }, - sameElse : 'L' - }, - relativeTime : { - future : '%s múlva', - past : '%s', - s : translate, - m : translate, - mm : translate, - h : translate, - hh : translate, - d : translate, - dd : translate, - M : translate, - MM : translate, - y : translate, - yy : translate - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Armenian (hy-am) -// author : Armendarabyan : https://github.com/armendarabyan - -(function (factory) { - factory(moment); -}(function (moment) { - function monthsCaseReplace(m, format) { - var months = { - 'nominative': 'հունվար_փետրվար_մարտ_ապրիլ_մայիս_հունիս_հուլիս_օգոստոս_սեպտեմբեր_հոկտեմբեր_նոյեմբեր_դեկտեմբեր'.split('_'), - 'accusative': 'հունվարի_փետրվարի_մարտի_ապրիլի_մայիսի_հունիսի_հուլիսի_օգոստոսի_սեպտեմբերի_հոկտեմբերի_նոյեմբերի_դեկտեմբերի'.split('_') - }, - - nounCase = (/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/).test(format) ? - 'accusative' : - 'nominative'; - - return months[nounCase][m.month()]; - } - - function monthsShortCaseReplace(m, format) { - var monthsShort = 'հնվ_փտր_մրտ_ապր_մյս_հնս_հլս_օգս_սպտ_հկտ_նմբ_դկտ'.split('_'); - - return monthsShort[m.month()]; - } - - function weekdaysCaseReplace(m, format) { - var weekdays = 'կիրակի_երկուշաբթի_երեքշաբթի_չորեքշաբթի_հինգշաբթի_ուրբաթ_շաբաթ'.split('_'); - - return weekdays[m.day()]; - } - - return moment.defineLocale('hy-am', { - months : monthsCaseReplace, - monthsShort : monthsShortCaseReplace, - weekdays : weekdaysCaseReplace, - weekdaysShort : 'կրկ_երկ_երք_չրք_հնգ_ուրբ_շբթ'.split('_'), - weekdaysMin : 'կրկ_երկ_երք_չրք_հնգ_ուրբ_շբթ'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD.MM.YYYY', - LL : 'D MMMM YYYY թ.', - LLL : 'D MMMM YYYY թ., LT', - LLLL : 'dddd, D MMMM YYYY թ., LT' - }, - calendar : { - sameDay: '[այսօր] LT', - nextDay: '[վաղը] LT', - lastDay: '[երեկ] LT', - nextWeek: function () { - return 'dddd [օրը ժամը] LT'; - }, - lastWeek: function () { - return '[անցած] dddd [օրը ժամը] LT'; - }, - sameElse: 'L' - }, - relativeTime : { - future : '%s հետո', - past : '%s առաջ', - s : 'մի քանի վայրկյան', - m : 'րոպե', - mm : '%d րոպե', - h : 'ժամ', - hh : '%d ժամ', - d : 'օր', - dd : '%d օր', - M : 'ամիս', - MM : '%d ամիս', - y : 'տարի', - yy : '%d տարի' - }, - - meridiem : function (hour) { - if (hour < 4) { - return 'գիշերվա'; - } else if (hour < 12) { - return 'առավոտվա'; - } else if (hour < 17) { - return 'ցերեկվա'; - } else { - return 'երեկոյան'; - } - }, - - ordinal: function (number, period) { - switch (period) { - case 'DDD': - case 'w': - case 'W': - case 'DDDo': - if (number === 1) { - return number + '-ին'; - } - return number + '-րդ'; - default: - return number; - } - }, - - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Bahasa Indonesia (id) -// author : Mohammad Satrio Utomo : https://github.com/tyok -// reference: http://id.wikisource.org/wiki/Pedoman_Umum_Ejaan_Bahasa_Indonesia_yang_Disempurnakan - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('id', { - months : 'Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember'.split('_'), - monthsShort : 'Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nov_Des'.split('_'), - weekdays : 'Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu'.split('_'), - weekdaysShort : 'Min_Sen_Sel_Rab_Kam_Jum_Sab'.split('_'), - weekdaysMin : 'Mg_Sn_Sl_Rb_Km_Jm_Sb'.split('_'), - longDateFormat : { - LT : 'HH.mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY [pukul] LT', - LLLL : 'dddd, D MMMM YYYY [pukul] LT' - }, - meridiem : function (hours, minutes, isLower) { - if (hours < 11) { - return 'pagi'; - } else if (hours < 15) { - return 'siang'; - } else if (hours < 19) { - return 'sore'; - } else { - return 'malam'; - } - }, - calendar : { - sameDay : '[Hari ini pukul] LT', - nextDay : '[Besok pukul] LT', - nextWeek : 'dddd [pukul] LT', - lastDay : '[Kemarin pukul] LT', - lastWeek : 'dddd [lalu pukul] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'dalam %s', - past : '%s yang lalu', - s : 'beberapa detik', - m : 'semenit', - mm : '%d menit', - h : 'sejam', - hh : '%d jam', - d : 'sehari', - dd : '%d hari', - M : 'sebulan', - MM : '%d bulan', - y : 'setahun', - yy : '%d tahun' - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : icelandic (is) -// author : Hinrik Örn Sigurðsson : https://github.com/hinrik - -(function (factory) { - factory(moment); -}(function (moment) { - function plural(n) { - if (n % 100 === 11) { - return true; - } else if (n % 10 === 1) { - return false; - } - return true; - } - - function translate(number, withoutSuffix, key, isFuture) { - var result = number + ' '; - switch (key) { - case 's': - return withoutSuffix || isFuture ? 'nokkrar sekúndur' : 'nokkrum sekúndum'; - case 'm': - return withoutSuffix ? 'mínúta' : 'mínútu'; - case 'mm': - if (plural(number)) { - return result + (withoutSuffix || isFuture ? 'mínútur' : 'mínútum'); - } else if (withoutSuffix) { - return result + 'mínúta'; - } - return result + 'mínútu'; - case 'hh': - if (plural(number)) { - return result + (withoutSuffix || isFuture ? 'klukkustundir' : 'klukkustundum'); - } - return result + 'klukkustund'; - case 'd': - if (withoutSuffix) { - return 'dagur'; - } - return isFuture ? 'dag' : 'degi'; - case 'dd': - if (plural(number)) { - if (withoutSuffix) { - return result + 'dagar'; - } - return result + (isFuture ? 'daga' : 'dögum'); - } else if (withoutSuffix) { - return result + 'dagur'; - } - return result + (isFuture ? 'dag' : 'degi'); - case 'M': - if (withoutSuffix) { - return 'mánuður'; - } - return isFuture ? 'mánuð' : 'mánuði'; - case 'MM': - if (plural(number)) { - if (withoutSuffix) { - return result + 'mánuðir'; - } - return result + (isFuture ? 'mánuði' : 'mánuðum'); - } else if (withoutSuffix) { - return result + 'mánuður'; - } - return result + (isFuture ? 'mánuð' : 'mánuði'); - case 'y': - return withoutSuffix || isFuture ? 'ár' : 'ári'; - case 'yy': - if (plural(number)) { - return result + (withoutSuffix || isFuture ? 'ár' : 'árum'); - } - return result + (withoutSuffix || isFuture ? 'ár' : 'ári'); - } - } - - return moment.defineLocale('is', { - months : 'janúar_febrúar_mars_apríl_maí_júní_júlí_ágúst_september_október_nóvember_desember'.split('_'), - monthsShort : 'jan_feb_mar_apr_maí_jún_júl_ágú_sep_okt_nóv_des'.split('_'), - weekdays : 'sunnudagur_mánudagur_þriðjudagur_miðvikudagur_fimmtudagur_föstudagur_laugardagur'.split('_'), - weekdaysShort : 'sun_mán_þri_mið_fim_fös_lau'.split('_'), - weekdaysMin : 'Su_Má_Þr_Mi_Fi_Fö_La'.split('_'), - longDateFormat : { - LT : 'H:mm', - L : 'DD/MM/YYYY', - LL : 'D. MMMM YYYY', - LLL : 'D. MMMM YYYY [kl.] LT', - LLLL : 'dddd, D. MMMM YYYY [kl.] LT' - }, - calendar : { - sameDay : '[í dag kl.] LT', - nextDay : '[á morgun kl.] LT', - nextWeek : 'dddd [kl.] LT', - lastDay : '[í gær kl.] LT', - lastWeek : '[síðasta] dddd [kl.] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'eftir %s', - past : 'fyrir %s síðan', - s : translate, - m : translate, - mm : translate, - h : 'klukkustund', - hh : translate, - d : translate, - dd : translate, - M : translate, - MM : translate, - y : translate, - yy : translate - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : italian (it) -// author : Lorenzo : https://github.com/aliem -// author: Mattia Larentis: https://github.com/nostalgiaz - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('it', { - months : 'gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre'.split('_'), - monthsShort : 'gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic'.split('_'), - weekdays : 'Domenica_Lunedì_Martedì_Mercoledì_Giovedì_Venerdì_Sabato'.split('_'), - weekdaysShort : 'Dom_Lun_Mar_Mer_Gio_Ven_Sab'.split('_'), - weekdaysMin : 'D_L_Ma_Me_G_V_S'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd, D MMMM YYYY LT' - }, - calendar : { - sameDay: '[Oggi alle] LT', - nextDay: '[Domani alle] LT', - nextWeek: 'dddd [alle] LT', - lastDay: '[Ieri alle] LT', - lastWeek: '[lo scorso] dddd [alle] LT', - sameElse: 'L' - }, - relativeTime : { - future : function (s) { - return ((/^[0-9].+$/).test(s) ? 'tra' : 'in') + ' ' + s; - }, - past : '%s fa', - s : 'alcuni secondi', - m : 'un minuto', - mm : '%d minuti', - h : 'un\'ora', - hh : '%d ore', - d : 'un giorno', - dd : '%d giorni', - M : 'un mese', - MM : '%d mesi', - y : 'un anno', - yy : '%d anni' - }, - ordinal: '%dº', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : japanese (ja) -// author : LI Long : https://github.com/baryon - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('ja', { - months : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), - monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), - weekdays : '日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日'.split('_'), - weekdaysShort : '日_月_火_水_木_金_土'.split('_'), - weekdaysMin : '日_月_火_水_木_金_土'.split('_'), - longDateFormat : { - LT : 'Ah時m分', - L : 'YYYY/MM/DD', - LL : 'YYYY年M月D日', - LLL : 'YYYY年M月D日LT', - LLLL : 'YYYY年M月D日LT dddd' - }, - meridiem : function (hour, minute, isLower) { - if (hour < 12) { - return '午前'; - } else { - return '午後'; - } - }, - calendar : { - sameDay : '[今日] LT', - nextDay : '[明日] LT', - nextWeek : '[来週]dddd LT', - lastDay : '[昨日] LT', - lastWeek : '[前週]dddd LT', - sameElse : 'L' - }, - relativeTime : { - future : '%s後', - past : '%s前', - s : '数秒', - m : '1分', - mm : '%d分', - h : '1時間', - hh : '%d時間', - d : '1日', - dd : '%d日', - M : '1ヶ月', - MM : '%dヶ月', - y : '1年', - yy : '%d年' - } - }); -})); -// moment.js locale configuration -// locale : Georgian (ka) -// author : Irakli Janiashvili : https://github.com/irakli-janiashvili - -(function (factory) { - factory(moment); -}(function (moment) { - function monthsCaseReplace(m, format) { - var months = { - 'nominative': 'იანვარი_თებერვალი_მარტი_აპრილი_მაისი_ივნისი_ივლისი_აგვისტო_სექტემბერი_ოქტომბერი_ნოემბერი_დეკემბერი'.split('_'), - 'accusative': 'იანვარს_თებერვალს_მარტს_აპრილის_მაისს_ივნისს_ივლისს_აგვისტს_სექტემბერს_ოქტომბერს_ნოემბერს_დეკემბერს'.split('_') - }, - - nounCase = (/D[oD] *MMMM?/).test(format) ? - 'accusative' : - 'nominative'; - - return months[nounCase][m.month()]; - } - - function weekdaysCaseReplace(m, format) { - var weekdays = { - 'nominative': 'კვირა_ორშაბათი_სამშაბათი_ოთხშაბათი_ხუთშაბათი_პარასკევი_შაბათი'.split('_'), - 'accusative': 'კვირას_ორშაბათს_სამშაბათს_ოთხშაბათს_ხუთშაბათს_პარასკევს_შაბათს'.split('_') - }, - - nounCase = (/(წინა|შემდეგ)/).test(format) ? - 'accusative' : - 'nominative'; - - return weekdays[nounCase][m.day()]; - } - - return moment.defineLocale('ka', { - months : monthsCaseReplace, - monthsShort : 'იან_თებ_მარ_აპრ_მაი_ივნ_ივლ_აგვ_სექ_ოქტ_ნოე_დეკ'.split('_'), - weekdays : weekdaysCaseReplace, - weekdaysShort : 'კვი_ორშ_სამ_ოთხ_ხუთ_პარ_შაბ'.split('_'), - weekdaysMin : 'კვ_ორ_სა_ოთ_ხუ_პა_შა'.split('_'), - longDateFormat : { - LT : 'h:mm A', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd, D MMMM YYYY LT' - }, - calendar : { - sameDay : '[დღეს] LT[-ზე]', - nextDay : '[ხვალ] LT[-ზე]', - lastDay : '[გუშინ] LT[-ზე]', - nextWeek : '[შემდეგ] dddd LT[-ზე]', - lastWeek : '[წინა] dddd LT-ზე', - sameElse : 'L' - }, - relativeTime : { - future : function (s) { - return (/(წამი|წუთი|საათი|წელი)/).test(s) ? - s.replace(/ი$/, 'ში') : - s + 'ში'; - }, - past : function (s) { - if ((/(წამი|წუთი|საათი|დღე|თვე)/).test(s)) { - return s.replace(/(ი|ე)$/, 'ის წინ'); - } - if ((/წელი/).test(s)) { - return s.replace(/წელი$/, 'წლის წინ'); - } - }, - s : 'რამდენიმე წამი', - m : 'წუთი', - mm : '%d წუთი', - h : 'საათი', - hh : '%d საათი', - d : 'დღე', - dd : '%d დღე', - M : 'თვე', - MM : '%d თვე', - y : 'წელი', - yy : '%d წელი' - }, - ordinal : function (number) { - if (number === 0) { - return number; - } - - if (number === 1) { - return number + '-ლი'; - } - - if ((number < 20) || (number <= 100 && (number % 20 === 0)) || (number % 100 === 0)) { - return 'მე-' + number; - } - - return number + '-ე'; - }, - week : { - dow : 1, - doy : 7 - } - }); -})); -// moment.js locale configuration -// locale : khmer (km) -// author : Kruy Vanna : https://github.com/kruyvanna - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('km', { - months: 'មករា_កុម្ភៈ_មិនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ'.split('_'), - monthsShort: 'មករា_កុម្ភៈ_មិនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ'.split('_'), - weekdays: 'អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍'.split('_'), - weekdaysShort: 'អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍'.split('_'), - weekdaysMin: 'អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍'.split('_'), - longDateFormat: { - LT: 'HH:mm', - L: 'DD/MM/YYYY', - LL: 'D MMMM YYYY', - LLL: 'D MMMM YYYY LT', - LLLL: 'dddd, D MMMM YYYY LT' - }, - calendar: { - sameDay: '[ថ្ងៃនៈ ម៉ោង] LT', - nextDay: '[ស្អែក ម៉ោង] LT', - nextWeek: 'dddd [ម៉ោង] LT', - lastDay: '[ម្សិលមិញ ម៉ោង] LT', - lastWeek: 'dddd [សប្តាហ៍មុន] [ម៉ោង] LT', - sameElse: 'L' - }, - relativeTime: { - future: '%sទៀត', - past: '%sមុន', - s: 'ប៉ុន្មានវិនាទី', - m: 'មួយនាទី', - mm: '%d នាទី', - h: 'មួយម៉ោង', - hh: '%d ម៉ោង', - d: 'មួយថ្ងៃ', - dd: '%d ថ្ងៃ', - M: 'មួយខែ', - MM: '%d ខែ', - y: 'មួយឆ្នាំ', - yy: '%d ឆ្នាំ' - }, - week: { - dow: 1, // Monday is the first day of the week. - doy: 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : korean (ko) -// -// authors -// -// - Kyungwook, Park : https://github.com/kyungw00k -// - Jeeeyul Lee -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('ko', { - months : '1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월'.split('_'), - monthsShort : '1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월'.split('_'), - weekdays : '일요일_월요일_화요일_수요일_목요일_금요일_토요일'.split('_'), - weekdaysShort : '일_월_화_수_목_금_토'.split('_'), - weekdaysMin : '일_월_화_수_목_금_토'.split('_'), - longDateFormat : { - LT : 'A h시 m분', - L : 'YYYY.MM.DD', - LL : 'YYYY년 MMMM D일', - LLL : 'YYYY년 MMMM D일 LT', - LLLL : 'YYYY년 MMMM D일 dddd LT' - }, - meridiem : function (hour, minute, isUpper) { - return hour < 12 ? '오전' : '오후'; - }, - calendar : { - sameDay : '오늘 LT', - nextDay : '내일 LT', - nextWeek : 'dddd LT', - lastDay : '어제 LT', - lastWeek : '지난주 dddd LT', - sameElse : 'L' - }, - relativeTime : { - future : '%s 후', - past : '%s 전', - s : '몇초', - ss : '%d초', - m : '일분', - mm : '%d분', - h : '한시간', - hh : '%d시간', - d : '하루', - dd : '%d일', - M : '한달', - MM : '%d달', - y : '일년', - yy : '%d년' - }, - ordinal : '%d일', - meridiemParse : /(오전|오후)/, - isPM : function (token) { - return token === '오후'; - } - }); -})); -// moment.js locale configuration -// locale : Luxembourgish (lb) -// author : mweimerskirch : https://github.com/mweimerskirch, David Raison : https://github.com/kwisatz - -// Note: Luxembourgish has a very particular phonological rule ('Eifeler Regel') that causes the -// deletion of the final 'n' in certain contexts. That's what the 'eifelerRegelAppliesToWeekday' -// and 'eifelerRegelAppliesToNumber' methods are meant for - -(function (factory) { - factory(moment); -}(function (moment) { - function processRelativeTime(number, withoutSuffix, key, isFuture) { - var format = { - 'm': ['eng Minutt', 'enger Minutt'], - 'h': ['eng Stonn', 'enger Stonn'], - 'd': ['een Dag', 'engem Dag'], - 'M': ['ee Mount', 'engem Mount'], - 'y': ['ee Joer', 'engem Joer'] - }; - return withoutSuffix ? format[key][0] : format[key][1]; - } - - function processFutureTime(string) { - var number = string.substr(0, string.indexOf(' ')); - if (eifelerRegelAppliesToNumber(number)) { - return 'a ' + string; - } - return 'an ' + string; - } - - function processPastTime(string) { - var number = string.substr(0, string.indexOf(' ')); - if (eifelerRegelAppliesToNumber(number)) { - return 'viru ' + string; - } - return 'virun ' + string; - } - - /** - * Returns true if the word before the given number loses the '-n' ending. - * e.g. 'an 10 Deeg' but 'a 5 Deeg' - * - * @param number {integer} - * @returns {boolean} - */ - function eifelerRegelAppliesToNumber(number) { - number = parseInt(number, 10); - if (isNaN(number)) { - return false; - } - if (number < 0) { - // Negative Number --> always true - return true; - } else if (number < 10) { - // Only 1 digit - if (4 <= number && number <= 7) { - return true; - } - return false; - } else if (number < 100) { - // 2 digits - var lastDigit = number % 10, firstDigit = number / 10; - if (lastDigit === 0) { - return eifelerRegelAppliesToNumber(firstDigit); - } - return eifelerRegelAppliesToNumber(lastDigit); - } else if (number < 10000) { - // 3 or 4 digits --> recursively check first digit - while (number >= 10) { - number = number / 10; - } - return eifelerRegelAppliesToNumber(number); - } else { - // Anything larger than 4 digits: recursively check first n-3 digits - number = number / 1000; - return eifelerRegelAppliesToNumber(number); - } - } - - return moment.defineLocale('lb', { - months: 'Januar_Februar_Mäerz_Abrëll_Mee_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'), - monthsShort: 'Jan._Febr._Mrz._Abr._Mee_Jun._Jul._Aug._Sept._Okt._Nov._Dez.'.split('_'), - weekdays: 'Sonndeg_Méindeg_Dënschdeg_Mëttwoch_Donneschdeg_Freideg_Samschdeg'.split('_'), - weekdaysShort: 'So._Mé._Dë._Më._Do._Fr._Sa.'.split('_'), - weekdaysMin: 'So_Mé_Dë_Më_Do_Fr_Sa'.split('_'), - longDateFormat: { - LT: 'H:mm [Auer]', - L: 'DD.MM.YYYY', - LL: 'D. MMMM YYYY', - LLL: 'D. MMMM YYYY LT', - LLLL: 'dddd, D. MMMM YYYY LT' - }, - calendar: { - sameDay: '[Haut um] LT', - sameElse: 'L', - nextDay: '[Muer um] LT', - nextWeek: 'dddd [um] LT', - lastDay: '[Gëschter um] LT', - lastWeek: function () { - // Different date string for 'Dënschdeg' (Tuesday) and 'Donneschdeg' (Thursday) due to phonological rule - switch (this.day()) { - case 2: - case 4: - return '[Leschten] dddd [um] LT'; - default: - return '[Leschte] dddd [um] LT'; - } - } - }, - relativeTime : { - future : processFutureTime, - past : processPastTime, - s : 'e puer Sekonnen', - m : processRelativeTime, - mm : '%d Minutten', - h : processRelativeTime, - hh : '%d Stonnen', - d : processRelativeTime, - dd : '%d Deeg', - M : processRelativeTime, - MM : '%d Méint', - y : processRelativeTime, - yy : '%d Joer' - }, - ordinal: '%d.', - week: { - dow: 1, // Monday is the first day of the week. - doy: 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Lithuanian (lt) -// author : Mindaugas Mozūras : https://github.com/mmozuras - -(function (factory) { - factory(moment); -}(function (moment) { - var units = { - 'm' : 'minutė_minutės_minutę', - 'mm': 'minutės_minučių_minutes', - 'h' : 'valanda_valandos_valandą', - 'hh': 'valandos_valandų_valandas', - 'd' : 'diena_dienos_dieną', - 'dd': 'dienos_dienų_dienas', - 'M' : 'mėnuo_mėnesio_mėnesį', - 'MM': 'mėnesiai_mėnesių_mėnesius', - 'y' : 'metai_metų_metus', - 'yy': 'metai_metų_metus' - }, - weekDays = 'sekmadienis_pirmadienis_antradienis_trečiadienis_ketvirtadienis_penktadienis_šeštadienis'.split('_'); - - function translateSeconds(number, withoutSuffix, key, isFuture) { - if (withoutSuffix) { - return 'kelios sekundės'; - } else { - return isFuture ? 'kelių sekundžių' : 'kelias sekundes'; - } - } - - function translateSingular(number, withoutSuffix, key, isFuture) { - return withoutSuffix ? forms(key)[0] : (isFuture ? forms(key)[1] : forms(key)[2]); - } - - function special(number) { - return number % 10 === 0 || (number > 10 && number < 20); - } - - function forms(key) { - return units[key].split('_'); - } - - function translate(number, withoutSuffix, key, isFuture) { - var result = number + ' '; - if (number === 1) { - return result + translateSingular(number, withoutSuffix, key[0], isFuture); - } else if (withoutSuffix) { - return result + (special(number) ? forms(key)[1] : forms(key)[0]); - } else { - if (isFuture) { - return result + forms(key)[1]; - } else { - return result + (special(number) ? forms(key)[1] : forms(key)[2]); - } - } - } - - function relativeWeekDay(moment, format) { - var nominative = format.indexOf('dddd HH:mm') === -1, - weekDay = weekDays[moment.day()]; - - return nominative ? weekDay : weekDay.substring(0, weekDay.length - 2) + 'į'; - } - - return moment.defineLocale('lt', { - months : 'sausio_vasario_kovo_balandžio_gegužės_birželio_liepos_rugpjūčio_rugsėjo_spalio_lapkričio_gruodžio'.split('_'), - monthsShort : 'sau_vas_kov_bal_geg_bir_lie_rgp_rgs_spa_lap_grd'.split('_'), - weekdays : relativeWeekDay, - weekdaysShort : 'Sek_Pir_Ant_Tre_Ket_Pen_Šeš'.split('_'), - weekdaysMin : 'S_P_A_T_K_Pn_Š'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'YYYY-MM-DD', - LL : 'YYYY [m.] MMMM D [d.]', - LLL : 'YYYY [m.] MMMM D [d.], LT [val.]', - LLLL : 'YYYY [m.] MMMM D [d.], dddd, LT [val.]', - l : 'YYYY-MM-DD', - ll : 'YYYY [m.] MMMM D [d.]', - lll : 'YYYY [m.] MMMM D [d.], LT [val.]', - llll : 'YYYY [m.] MMMM D [d.], ddd, LT [val.]' - }, - calendar : { - sameDay : '[Šiandien] LT', - nextDay : '[Rytoj] LT', - nextWeek : 'dddd LT', - lastDay : '[Vakar] LT', - lastWeek : '[Praėjusį] dddd LT', - sameElse : 'L' - }, - relativeTime : { - future : 'po %s', - past : 'prieš %s', - s : translateSeconds, - m : translateSingular, - mm : translate, - h : translateSingular, - hh : translate, - d : translateSingular, - dd : translate, - M : translateSingular, - MM : translate, - y : translateSingular, - yy : translate - }, - ordinal : function (number) { - return number + '-oji'; - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : latvian (lv) -// author : Kristaps Karlsons : https://github.com/skakri - -(function (factory) { - factory(moment); -}(function (moment) { - var units = { - 'mm': 'minūti_minūtes_minūte_minūtes', - 'hh': 'stundu_stundas_stunda_stundas', - 'dd': 'dienu_dienas_diena_dienas', - 'MM': 'mēnesi_mēnešus_mēnesis_mēneši', - 'yy': 'gadu_gadus_gads_gadi' - }; - - function format(word, number, withoutSuffix) { - var forms = word.split('_'); - if (withoutSuffix) { - return number % 10 === 1 && number !== 11 ? forms[2] : forms[3]; - } else { - return number % 10 === 1 && number !== 11 ? forms[0] : forms[1]; - } - } - - function relativeTimeWithPlural(number, withoutSuffix, key) { - return number + ' ' + format(units[key], number, withoutSuffix); - } - - return moment.defineLocale('lv', { - months : 'janvāris_februāris_marts_aprīlis_maijs_jūnijs_jūlijs_augusts_septembris_oktobris_novembris_decembris'.split('_'), - monthsShort : 'jan_feb_mar_apr_mai_jūn_jūl_aug_sep_okt_nov_dec'.split('_'), - weekdays : 'svētdiena_pirmdiena_otrdiena_trešdiena_ceturtdiena_piektdiena_sestdiena'.split('_'), - weekdaysShort : 'Sv_P_O_T_C_Pk_S'.split('_'), - weekdaysMin : 'Sv_P_O_T_C_Pk_S'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD.MM.YYYY', - LL : 'YYYY. [gada] D. MMMM', - LLL : 'YYYY. [gada] D. MMMM, LT', - LLLL : 'YYYY. [gada] D. MMMM, dddd, LT' - }, - calendar : { - sameDay : '[Šodien pulksten] LT', - nextDay : '[Rīt pulksten] LT', - nextWeek : 'dddd [pulksten] LT', - lastDay : '[Vakar pulksten] LT', - lastWeek : '[Pagājušā] dddd [pulksten] LT', - sameElse : 'L' - }, - relativeTime : { - future : '%s vēlāk', - past : '%s agrāk', - s : 'dažas sekundes', - m : 'minūti', - mm : relativeTimeWithPlural, - h : 'stundu', - hh : relativeTimeWithPlural, - d : 'dienu', - dd : relativeTimeWithPlural, - M : 'mēnesi', - MM : relativeTimeWithPlural, - y : 'gadu', - yy : relativeTimeWithPlural - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : macedonian (mk) -// author : Borislav Mickov : https://github.com/B0k0 - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('mk', { - months : 'јануари_февруари_март_април_мај_јуни_јули_август_септември_октомври_ноември_декември'.split('_'), - monthsShort : 'јан_фев_мар_апр_мај_јун_јул_авг_сеп_окт_ное_дек'.split('_'), - weekdays : 'недела_понеделник_вторник_среда_четврток_петок_сабота'.split('_'), - weekdaysShort : 'нед_пон_вто_сре_чет_пет_саб'.split('_'), - weekdaysMin : 'нe_пo_вт_ср_че_пе_сa'.split('_'), - longDateFormat : { - LT : 'H:mm', - L : 'D.MM.YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd, D MMMM YYYY LT' - }, - calendar : { - sameDay : '[Денес во] LT', - nextDay : '[Утре во] LT', - nextWeek : 'dddd [во] LT', - lastDay : '[Вчера во] LT', - lastWeek : function () { - switch (this.day()) { - case 0: - case 3: - case 6: - return '[Во изминатата] dddd [во] LT'; - case 1: - case 2: - case 4: - case 5: - return '[Во изминатиот] dddd [во] LT'; - } - }, - sameElse : 'L' - }, - relativeTime : { - future : 'после %s', - past : 'пред %s', - s : 'неколку секунди', - m : 'минута', - mm : '%d минути', - h : 'час', - hh : '%d часа', - d : 'ден', - dd : '%d дена', - M : 'месец', - MM : '%d месеци', - y : 'година', - yy : '%d години' - }, - ordinal : function (number) { - var lastDigit = number % 10, - last2Digits = number % 100; - if (number === 0) { - return number + '-ев'; - } else if (last2Digits === 0) { - return number + '-ен'; - } else if (last2Digits > 10 && last2Digits < 20) { - return number + '-ти'; - } else if (lastDigit === 1) { - return number + '-ви'; - } else if (lastDigit === 2) { - return number + '-ри'; - } else if (lastDigit === 7 || lastDigit === 8) { - return number + '-ми'; - } else { - return number + '-ти'; - } - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : malayalam (ml) -// author : Floyd Pink : https://github.com/floydpink - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('ml', { - months : 'ജനുവരി_ഫെബ്രുവരി_മാർച്ച്_ഏപ്രിൽ_മേയ്_ജൂൺ_ജൂലൈ_ഓഗസ്റ്റ്_സെപ്റ്റംബർ_ഒക്ടോബർ_നവംബർ_ഡിസംബർ'.split('_'), - monthsShort : 'ജനു._ഫെബ്രു._മാർ._ഏപ്രി._മേയ്_ജൂൺ_ജൂലൈ._ഓഗ._സെപ്റ്റ._ഒക്ടോ._നവം._ഡിസം.'.split('_'), - weekdays : 'ഞായറാഴ്ച_തിങ്കളാഴ്ച_ചൊവ്വാഴ്ച_ബുധനാഴ്ച_വ്യാഴാഴ്ച_വെള്ളിയാഴ്ച_ശനിയാഴ്ച'.split('_'), - weekdaysShort : 'ഞായർ_തിങ്കൾ_ചൊവ്വ_ബുധൻ_വ്യാഴം_വെള്ളി_ശനി'.split('_'), - weekdaysMin : 'ഞാ_തി_ചൊ_ബു_വ്യാ_വെ_ശ'.split('_'), - longDateFormat : { - LT : 'A h:mm -നു', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY, LT', - LLLL : 'dddd, D MMMM YYYY, LT' - }, - calendar : { - sameDay : '[ഇന്ന്] LT', - nextDay : '[നാളെ] LT', - nextWeek : 'dddd, LT', - lastDay : '[ഇന്നലെ] LT', - lastWeek : '[കഴിഞ്ഞ] dddd, LT', - sameElse : 'L' - }, - relativeTime : { - future : '%s കഴിഞ്ഞ്', - past : '%s മുൻപ്', - s : 'അൽപ നിമിഷങ്ങൾ', - m : 'ഒരു മിനിറ്റ്', - mm : '%d മിനിറ്റ്', - h : 'ഒരു മണിക്കൂർ', - hh : '%d മണിക്കൂർ', - d : 'ഒരു ദിവസം', - dd : '%d ദിവസം', - M : 'ഒരു മാസം', - MM : '%d മാസം', - y : 'ഒരു വർഷം', - yy : '%d വർഷം' - }, - meridiem : function (hour, minute, isLower) { - if (hour < 4) { - return 'രാത്രി'; - } else if (hour < 12) { - return 'രാവിലെ'; - } else if (hour < 17) { - return 'ഉച്ച കഴിഞ്ഞ്'; - } else if (hour < 20) { - return 'വൈകുന്നേരം'; - } else { - return 'രാത്രി'; - } - } - }); -})); -// moment.js locale configuration -// locale : Marathi (mr) -// author : Harshad Kale : https://github.com/kalehv - -(function (factory) { - factory(moment); -}(function (moment) { - var symbolMap = { - '1': '१', - '2': '२', - '3': '३', - '4': '४', - '5': '५', - '6': '६', - '7': '७', - '8': '८', - '9': '९', - '0': '०' - }, - numberMap = { - '१': '1', - '२': '2', - '३': '3', - '४': '4', - '५': '5', - '६': '6', - '७': '7', - '८': '8', - '९': '9', - '०': '0' - }; - - return moment.defineLocale('mr', { - months : 'जानेवारी_फेब्रुवारी_मार्च_एप्रिल_मे_जून_जुलै_ऑगस्ट_सप्टेंबर_ऑक्टोबर_नोव्हेंबर_डिसेंबर'.split('_'), - monthsShort: 'जाने._फेब्रु._मार्च._एप्रि._मे._जून._जुलै._ऑग._सप्टें._ऑक्टो._नोव्हें._डिसें.'.split('_'), - weekdays : 'रविवार_सोमवार_मंगळवार_बुधवार_गुरूवार_शुक्रवार_शनिवार'.split('_'), - weekdaysShort : 'रवि_सोम_मंगळ_बुध_गुरू_शुक्र_शनि'.split('_'), - weekdaysMin : 'र_सो_मं_बु_गु_शु_श'.split('_'), - longDateFormat : { - LT : 'A h:mm वाजता', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY, LT', - LLLL : 'dddd, D MMMM YYYY, LT' - }, - calendar : { - sameDay : '[आज] LT', - nextDay : '[उद्या] LT', - nextWeek : 'dddd, LT', - lastDay : '[काल] LT', - lastWeek: '[मागील] dddd, LT', - sameElse : 'L' - }, - relativeTime : { - future : '%s नंतर', - past : '%s पूर्वी', - s : 'सेकंद', - m: 'एक मिनिट', - mm: '%d मिनिटे', - h : 'एक तास', - hh : '%d तास', - d : 'एक दिवस', - dd : '%d दिवस', - M : 'एक महिना', - MM : '%d महिने', - y : 'एक वर्ष', - yy : '%d वर्षे' - }, - preparse: function (string) { - return string.replace(/[१२३४५६७८९०]/g, function (match) { - return numberMap[match]; - }); - }, - postformat: function (string) { - return string.replace(/\d/g, function (match) { - return symbolMap[match]; - }); - }, - meridiem: function (hour, minute, isLower) - { - if (hour < 4) { - return 'रात्री'; - } else if (hour < 10) { - return 'सकाळी'; - } else if (hour < 17) { - return 'दुपारी'; - } else if (hour < 20) { - return 'सायंकाळी'; - } else { - return 'रात्री'; - } - }, - week : { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Bahasa Malaysia (ms-MY) -// author : Weldan Jamili : https://github.com/weldan - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('ms-my', { - months : 'Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember'.split('_'), - monthsShort : 'Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis'.split('_'), - weekdays : 'Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu'.split('_'), - weekdaysShort : 'Ahd_Isn_Sel_Rab_Kha_Jum_Sab'.split('_'), - weekdaysMin : 'Ah_Is_Sl_Rb_Km_Jm_Sb'.split('_'), - longDateFormat : { - LT : 'HH.mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY [pukul] LT', - LLLL : 'dddd, D MMMM YYYY [pukul] LT' - }, - meridiem : function (hours, minutes, isLower) { - if (hours < 11) { - return 'pagi'; - } else if (hours < 15) { - return 'tengahari'; - } else if (hours < 19) { - return 'petang'; - } else { - return 'malam'; - } - }, - calendar : { - sameDay : '[Hari ini pukul] LT', - nextDay : '[Esok pukul] LT', - nextWeek : 'dddd [pukul] LT', - lastDay : '[Kelmarin pukul] LT', - lastWeek : 'dddd [lepas pukul] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'dalam %s', - past : '%s yang lepas', - s : 'beberapa saat', - m : 'seminit', - mm : '%d minit', - h : 'sejam', - hh : '%d jam', - d : 'sehari', - dd : '%d hari', - M : 'sebulan', - MM : '%d bulan', - y : 'setahun', - yy : '%d tahun' - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Burmese (my) -// author : Squar team, mysquar.com - -(function (factory) { - factory(moment); -}(function (moment) { - var symbolMap = { - '1': '၁', - '2': '၂', - '3': '၃', - '4': '၄', - '5': '၅', - '6': '၆', - '7': '၇', - '8': '၈', - '9': '၉', - '0': '၀' - }, numberMap = { - '၁': '1', - '၂': '2', - '၃': '3', - '၄': '4', - '၅': '5', - '၆': '6', - '၇': '7', - '၈': '8', - '၉': '9', - '၀': '0' - }; - return moment.defineLocale('my', { - months: 'ဇန်နဝါရီ_ဖေဖော်ဝါရီ_မတ်_ဧပြီ_မေ_ဇွန်_ဇူလိုင်_သြဂုတ်_စက်တင်ဘာ_အောက်တိုဘာ_နိုဝင်ဘာ_ဒီဇင်ဘာ'.split('_'), - monthsShort: 'ဇန်_ဖေ_မတ်_ပြီ_မေ_ဇွန်_လိုင်_သြ_စက်_အောက်_နို_ဒီ'.split('_'), - weekdays: 'တနင်္ဂနွေ_တနင်္လာ_အင်္ဂါ_ဗုဒ္ဓဟူး_ကြာသပတေး_သောကြာ_စနေ'.split('_'), - weekdaysShort: 'နွေ_လာ_င်္ဂါ_ဟူး_ကြာ_သော_နေ'.split('_'), - weekdaysMin: 'နွေ_လာ_င်္ဂါ_ဟူး_ကြာ_သော_နေ'.split('_'), - longDateFormat: { - LT: 'HH:mm', - L: 'DD/MM/YYYY', - LL: 'D MMMM YYYY', - LLL: 'D MMMM YYYY LT', - LLLL: 'dddd D MMMM YYYY LT' - }, - calendar: { - sameDay: '[ယနေ.] LT [မှာ]', - nextDay: '[မနက်ဖြန်] LT [မှာ]', - nextWeek: 'dddd LT [မှာ]', - lastDay: '[မနေ.က] LT [မှာ]', - lastWeek: '[ပြီးခဲ့သော] dddd LT [မှာ]', - sameElse: 'L' - }, - relativeTime: { - future: 'လာမည့် %s မှာ', - past: 'လွန်ခဲ့သော %s က', - s: 'စက္ကန်.အနည်းငယ်', - m: 'တစ်မိနစ်', - mm: '%d မိနစ်', - h: 'တစ်နာရီ', - hh: '%d နာရီ', - d: 'တစ်ရက်', - dd: '%d ရက်', - M: 'တစ်လ', - MM: '%d လ', - y: 'တစ်နှစ်', - yy: '%d နှစ်' - }, - preparse: function (string) { - return string.replace(/[၁၂၃၄၅၆၇၈၉၀]/g, function (match) { - return numberMap[match]; - }); - }, - postformat: function (string) { - return string.replace(/\d/g, function (match) { - return symbolMap[match]; - }); - }, - week: { - dow: 1, // Monday is the first day of the week. - doy: 4 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : norwegian bokmål (nb) -// authors : Espen Hovlandsdal : https://github.com/rexxars -// Sigurd Gartmann : https://github.com/sigurdga - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('nb', { - months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'), - monthsShort : 'jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des'.split('_'), - weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'), - weekdaysShort : 'søn_man_tirs_ons_tors_fre_lør'.split('_'), - weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'), - longDateFormat : { - LT : 'H.mm', - L : 'DD.MM.YYYY', - LL : 'D. MMMM YYYY', - LLL : 'D. MMMM YYYY [kl.] LT', - LLLL : 'dddd D. MMMM YYYY [kl.] LT' - }, - calendar : { - sameDay: '[i dag kl.] LT', - nextDay: '[i morgen kl.] LT', - nextWeek: 'dddd [kl.] LT', - lastDay: '[i går kl.] LT', - lastWeek: '[forrige] dddd [kl.] LT', - sameElse: 'L' - }, - relativeTime : { - future : 'om %s', - past : 'for %s siden', - s : 'noen sekunder', - m : 'ett minutt', - mm : '%d minutter', - h : 'en time', - hh : '%d timer', - d : 'en dag', - dd : '%d dager', - M : 'en måned', - MM : '%d måneder', - y : 'ett år', - yy : '%d år' - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : nepali/nepalese -// author : suvash : https://github.com/suvash - -(function (factory) { - factory(moment); -}(function (moment) { - var symbolMap = { - '1': '१', - '2': '२', - '3': '३', - '4': '४', - '5': '५', - '6': '६', - '7': '७', - '8': '८', - '9': '९', - '0': '०' - }, - numberMap = { - '१': '1', - '२': '2', - '३': '3', - '४': '4', - '५': '5', - '६': '6', - '७': '7', - '८': '8', - '९': '9', - '०': '0' - }; - - return moment.defineLocale('ne', { - months : 'जनवरी_फेब्रुवरी_मार्च_अप्रिल_मई_जुन_जुलाई_अगष्ट_सेप्टेम्बर_अक्टोबर_नोभेम्बर_डिसेम्बर'.split('_'), - monthsShort : 'जन._फेब्रु._मार्च_अप्रि._मई_जुन_जुलाई._अग._सेप्ट._अक्टो._नोभे._डिसे.'.split('_'), - weekdays : 'आइतबार_सोमबार_मङ्गलबार_बुधबार_बिहिबार_शुक्रबार_शनिबार'.split('_'), - weekdaysShort : 'आइत._सोम._मङ्गल._बुध._बिहि._शुक्र._शनि.'.split('_'), - weekdaysMin : 'आइ._सो._मङ्_बु._बि._शु._श.'.split('_'), - longDateFormat : { - LT : 'Aको h:mm बजे', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY, LT', - LLLL : 'dddd, D MMMM YYYY, LT' - }, - preparse: function (string) { - return string.replace(/[१२३४५६७८९०]/g, function (match) { - return numberMap[match]; - }); - }, - postformat: function (string) { - return string.replace(/\d/g, function (match) { - return symbolMap[match]; - }); - }, - meridiem : function (hour, minute, isLower) { - if (hour < 3) { - return 'राती'; - } else if (hour < 10) { - return 'बिहान'; - } else if (hour < 15) { - return 'दिउँसो'; - } else if (hour < 18) { - return 'बेलुका'; - } else if (hour < 20) { - return 'साँझ'; - } else { - return 'राती'; - } - }, - calendar : { - sameDay : '[आज] LT', - nextDay : '[भोली] LT', - nextWeek : '[आउँदो] dddd[,] LT', - lastDay : '[हिजो] LT', - lastWeek : '[गएको] dddd[,] LT', - sameElse : 'L' - }, - relativeTime : { - future : '%sमा', - past : '%s अगाडी', - s : 'केही समय', - m : 'एक मिनेट', - mm : '%d मिनेट', - h : 'एक घण्टा', - hh : '%d घण्टा', - d : 'एक दिन', - dd : '%d दिन', - M : 'एक महिना', - MM : '%d महिना', - y : 'एक बर्ष', - yy : '%d बर्ष' - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : dutch (nl) -// author : Joris Röling : https://github.com/jjupiter - -(function (factory) { - factory(moment); -}(function (moment) { - var monthsShortWithDots = 'jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.'.split('_'), - monthsShortWithoutDots = 'jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec'.split('_'); - - return moment.defineLocale('nl', { - months : 'januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december'.split('_'), - monthsShort : function (m, format) { - if (/-MMM-/.test(format)) { - return monthsShortWithoutDots[m.month()]; - } else { - return monthsShortWithDots[m.month()]; - } - }, - weekdays : 'zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag'.split('_'), - weekdaysShort : 'zo._ma._di._wo._do._vr._za.'.split('_'), - weekdaysMin : 'Zo_Ma_Di_Wo_Do_Vr_Za'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD-MM-YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd D MMMM YYYY LT' - }, - calendar : { - sameDay: '[vandaag om] LT', - nextDay: '[morgen om] LT', - nextWeek: 'dddd [om] LT', - lastDay: '[gisteren om] LT', - lastWeek: '[afgelopen] dddd [om] LT', - sameElse: 'L' - }, - relativeTime : { - future : 'over %s', - past : '%s geleden', - s : 'een paar seconden', - m : 'één minuut', - mm : '%d minuten', - h : 'één uur', - hh : '%d uur', - d : 'één dag', - dd : '%d dagen', - M : 'één maand', - MM : '%d maanden', - y : 'één jaar', - yy : '%d jaar' - }, - ordinal : function (number) { - return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : norwegian nynorsk (nn) -// author : https://github.com/mechuwind - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('nn', { - months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'), - monthsShort : 'jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des'.split('_'), - weekdays : 'sundag_måndag_tysdag_onsdag_torsdag_fredag_laurdag'.split('_'), - weekdaysShort : 'sun_mån_tys_ons_tor_fre_lau'.split('_'), - weekdaysMin : 'su_må_ty_on_to_fr_lø'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD.MM.YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd D MMMM YYYY LT' - }, - calendar : { - sameDay: '[I dag klokka] LT', - nextDay: '[I morgon klokka] LT', - nextWeek: 'dddd [klokka] LT', - lastDay: '[I går klokka] LT', - lastWeek: '[Føregåande] dddd [klokka] LT', - sameElse: 'L' - }, - relativeTime : { - future : 'om %s', - past : 'for %s sidan', - s : 'nokre sekund', - m : 'eit minutt', - mm : '%d minutt', - h : 'ein time', - hh : '%d timar', - d : 'ein dag', - dd : '%d dagar', - M : 'ein månad', - MM : '%d månader', - y : 'eit år', - yy : '%d år' - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : polish (pl) -// author : Rafal Hirsz : https://github.com/evoL - -(function (factory) { - factory(moment); -}(function (moment) { - var monthsNominative = 'styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień'.split('_'), - monthsSubjective = 'stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia'.split('_'); - - function plural(n) { - return (n % 10 < 5) && (n % 10 > 1) && ((~~(n / 10) % 10) !== 1); - } - - function translate(number, withoutSuffix, key) { - var result = number + ' '; - switch (key) { - case 'm': - return withoutSuffix ? 'minuta' : 'minutę'; - case 'mm': - return result + (plural(number) ? 'minuty' : 'minut'); - case 'h': - return withoutSuffix ? 'godzina' : 'godzinę'; - case 'hh': - return result + (plural(number) ? 'godziny' : 'godzin'); - case 'MM': - return result + (plural(number) ? 'miesiące' : 'miesięcy'); - case 'yy': - return result + (plural(number) ? 'lata' : 'lat'); - } - } - - return moment.defineLocale('pl', { - months : function (momentToFormat, format) { - if (/D MMMM/.test(format)) { - return monthsSubjective[momentToFormat.month()]; - } else { - return monthsNominative[momentToFormat.month()]; - } - }, - monthsShort : 'sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru'.split('_'), - weekdays : 'niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota'.split('_'), - weekdaysShort : 'nie_pon_wt_śr_czw_pt_sb'.split('_'), - weekdaysMin : 'N_Pn_Wt_Śr_Cz_Pt_So'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD.MM.YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd, D MMMM YYYY LT' - }, - calendar : { - sameDay: '[Dziś o] LT', - nextDay: '[Jutro o] LT', - nextWeek: '[W] dddd [o] LT', - lastDay: '[Wczoraj o] LT', - lastWeek: function () { - switch (this.day()) { - case 0: - return '[W zeszłą niedzielę o] LT'; - case 3: - return '[W zeszłą środę o] LT'; - case 6: - return '[W zeszłą sobotę o] LT'; - default: - return '[W zeszły] dddd [o] LT'; - } - }, - sameElse: 'L' - }, - relativeTime : { - future : 'za %s', - past : '%s temu', - s : 'kilka sekund', - m : translate, - mm : translate, - h : translate, - hh : translate, - d : '1 dzień', - dd : '%d dni', - M : 'miesiąc', - MM : translate, - y : 'rok', - yy : translate - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : brazilian portuguese (pt-br) -// author : Caio Ribeiro Pereira : https://github.com/caio-ribeiro-pereira - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('pt-br', { - months : 'janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro'.split('_'), - monthsShort : 'jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez'.split('_'), - weekdays : 'domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado'.split('_'), - weekdaysShort : 'dom_seg_ter_qua_qui_sex_sáb'.split('_'), - weekdaysMin : 'dom_2ª_3ª_4ª_5ª_6ª_sáb'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D [de] MMMM [de] YYYY', - LLL : 'D [de] MMMM [de] YYYY [às] LT', - LLLL : 'dddd, D [de] MMMM [de] YYYY [às] LT' - }, - calendar : { - sameDay: '[Hoje às] LT', - nextDay: '[Amanhã às] LT', - nextWeek: 'dddd [às] LT', - lastDay: '[Ontem às] LT', - lastWeek: function () { - return (this.day() === 0 || this.day() === 6) ? - '[Último] dddd [às] LT' : // Saturday + Sunday - '[Última] dddd [às] LT'; // Monday - Friday - }, - sameElse: 'L' - }, - relativeTime : { - future : 'em %s', - past : '%s atrás', - s : 'segundos', - m : 'um minuto', - mm : '%d minutos', - h : 'uma hora', - hh : '%d horas', - d : 'um dia', - dd : '%d dias', - M : 'um mês', - MM : '%d meses', - y : 'um ano', - yy : '%d anos' - }, - ordinal : '%dº' - }); -})); -// moment.js locale configuration -// locale : portuguese (pt) -// author : Jefferson : https://github.com/jalex79 - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('pt', { - months : 'janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro'.split('_'), - monthsShort : 'jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez'.split('_'), - weekdays : 'domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado'.split('_'), - weekdaysShort : 'dom_seg_ter_qua_qui_sex_sáb'.split('_'), - weekdaysMin : 'dom_2ª_3ª_4ª_5ª_6ª_sáb'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D [de] MMMM [de] YYYY', - LLL : 'D [de] MMMM [de] YYYY LT', - LLLL : 'dddd, D [de] MMMM [de] YYYY LT' - }, - calendar : { - sameDay: '[Hoje às] LT', - nextDay: '[Amanhã às] LT', - nextWeek: 'dddd [às] LT', - lastDay: '[Ontem às] LT', - lastWeek: function () { - return (this.day() === 0 || this.day() === 6) ? - '[Último] dddd [às] LT' : // Saturday + Sunday - '[Última] dddd [às] LT'; // Monday - Friday - }, - sameElse: 'L' - }, - relativeTime : { - future : 'em %s', - past : 'há %s', - s : 'segundos', - m : 'um minuto', - mm : '%d minutos', - h : 'uma hora', - hh : '%d horas', - d : 'um dia', - dd : '%d dias', - M : 'um mês', - MM : '%d meses', - y : 'um ano', - yy : '%d anos' - }, - ordinal : '%dº', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : romanian (ro) -// author : Vlad Gurdiga : https://github.com/gurdiga -// author : Valentin Agachi : https://github.com/avaly - -(function (factory) { - factory(moment); -}(function (moment) { - function relativeTimeWithPlural(number, withoutSuffix, key) { - var format = { - 'mm': 'minute', - 'hh': 'ore', - 'dd': 'zile', - 'MM': 'luni', - 'yy': 'ani' - }, - separator = ' '; - if (number % 100 >= 20 || (number >= 100 && number % 100 === 0)) { - separator = ' de '; - } - - return number + separator + format[key]; - } - - return moment.defineLocale('ro', { - months : 'ianuarie_februarie_martie_aprilie_mai_iunie_iulie_august_septembrie_octombrie_noiembrie_decembrie'.split('_'), - monthsShort : 'ian._febr._mart._apr._mai_iun._iul._aug._sept._oct._nov._dec.'.split('_'), - weekdays : 'duminică_luni_marți_miercuri_joi_vineri_sâmbătă'.split('_'), - weekdaysShort : 'Dum_Lun_Mar_Mie_Joi_Vin_Sâm'.split('_'), - weekdaysMin : 'Du_Lu_Ma_Mi_Jo_Vi_Sâ'.split('_'), - longDateFormat : { - LT : 'H:mm', - L : 'DD.MM.YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY H:mm', - LLLL : 'dddd, D MMMM YYYY H:mm' - }, - calendar : { - sameDay: '[azi la] LT', - nextDay: '[mâine la] LT', - nextWeek: 'dddd [la] LT', - lastDay: '[ieri la] LT', - lastWeek: '[fosta] dddd [la] LT', - sameElse: 'L' - }, - relativeTime : { - future : 'peste %s', - past : '%s în urmă', - s : 'câteva secunde', - m : 'un minut', - mm : relativeTimeWithPlural, - h : 'o oră', - hh : relativeTimeWithPlural, - d : 'o zi', - dd : relativeTimeWithPlural, - M : 'o lună', - MM : relativeTimeWithPlural, - y : 'un an', - yy : relativeTimeWithPlural - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : russian (ru) -// author : Viktorminator : https://github.com/Viktorminator -// Author : Menelion Elensúle : https://github.com/Oire - -(function (factory) { - factory(moment); -}(function (moment) { - function plural(word, num) { - var forms = word.split('_'); - return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]); - } - - function relativeTimeWithPlural(number, withoutSuffix, key) { - var format = { - 'mm': withoutSuffix ? 'минута_минуты_минут' : 'минуту_минуты_минут', - 'hh': 'час_часа_часов', - 'dd': 'день_дня_дней', - 'MM': 'месяц_месяца_месяцев', - 'yy': 'год_года_лет' - }; - if (key === 'm') { - return withoutSuffix ? 'минута' : 'минуту'; - } - else { - return number + ' ' + plural(format[key], +number); - } - } - - function monthsCaseReplace(m, format) { - var months = { - 'nominative': 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_'), - 'accusative': 'января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря'.split('_') - }, - - nounCase = (/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/).test(format) ? - 'accusative' : - 'nominative'; - - return months[nounCase][m.month()]; - } - - function monthsShortCaseReplace(m, format) { - var monthsShort = { - 'nominative': 'янв_фев_мар_апр_май_июнь_июль_авг_сен_окт_ноя_дек'.split('_'), - 'accusative': 'янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек'.split('_') - }, - - nounCase = (/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/).test(format) ? - 'accusative' : - 'nominative'; - - return monthsShort[nounCase][m.month()]; - } - - function weekdaysCaseReplace(m, format) { - var weekdays = { - 'nominative': 'воскресенье_понедельник_вторник_среда_четверг_пятница_суббота'.split('_'), - 'accusative': 'воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу'.split('_') - }, - - nounCase = (/\[ ?[Вв] ?(?:прошлую|следующую)? ?\] ?dddd/).test(format) ? - 'accusative' : - 'nominative'; - - return weekdays[nounCase][m.day()]; - } - - return moment.defineLocale('ru', { - months : monthsCaseReplace, - monthsShort : monthsShortCaseReplace, - weekdays : weekdaysCaseReplace, - weekdaysShort : 'вс_пн_вт_ср_чт_пт_сб'.split('_'), - weekdaysMin : 'вс_пн_вт_ср_чт_пт_сб'.split('_'), - monthsParse : [/^янв/i, /^фев/i, /^мар/i, /^апр/i, /^ма[й|я]/i, /^июн/i, /^июл/i, /^авг/i, /^сен/i, /^окт/i, /^ноя/i, /^дек/i], - longDateFormat : { - LT : 'HH:mm', - L : 'DD.MM.YYYY', - LL : 'D MMMM YYYY г.', - LLL : 'D MMMM YYYY г., LT', - LLLL : 'dddd, D MMMM YYYY г., LT' - }, - calendar : { - sameDay: '[Сегодня в] LT', - nextDay: '[Завтра в] LT', - lastDay: '[Вчера в] LT', - nextWeek: function () { - return this.day() === 2 ? '[Во] dddd [в] LT' : '[В] dddd [в] LT'; - }, - lastWeek: function () { - switch (this.day()) { - case 0: - return '[В прошлое] dddd [в] LT'; - case 1: - case 2: - case 4: - return '[В прошлый] dddd [в] LT'; - case 3: - case 5: - case 6: - return '[В прошлую] dddd [в] LT'; - } - }, - sameElse: 'L' - }, - relativeTime : { - future : 'через %s', - past : '%s назад', - s : 'несколько секунд', - m : relativeTimeWithPlural, - mm : relativeTimeWithPlural, - h : 'час', - hh : relativeTimeWithPlural, - d : 'день', - dd : relativeTimeWithPlural, - M : 'месяц', - MM : relativeTimeWithPlural, - y : 'год', - yy : relativeTimeWithPlural - }, - - meridiemParse: /ночи|утра|дня|вечера/i, - isPM : function (input) { - return /^(дня|вечера)$/.test(input); - }, - - meridiem : function (hour, minute, isLower) { - if (hour < 4) { - return 'ночи'; - } else if (hour < 12) { - return 'утра'; - } else if (hour < 17) { - return 'дня'; - } else { - return 'вечера'; - } - }, - - ordinal: function (number, period) { - switch (period) { - case 'M': - case 'd': - case 'DDD': - return number + '-й'; - case 'D': - return number + '-го'; - case 'w': - case 'W': - return number + '-я'; - default: - return number; - } - }, - - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : slovak (sk) -// author : Martin Minka : https://github.com/k2s -// based on work of petrbela : https://github.com/petrbela - -(function (factory) { - factory(moment); -}(function (moment) { - var months = 'január_február_marec_apríl_máj_jún_júl_august_september_október_november_december'.split('_'), - monthsShort = 'jan_feb_mar_apr_máj_jún_júl_aug_sep_okt_nov_dec'.split('_'); - - function plural(n) { - return (n > 1) && (n < 5); - } - - function translate(number, withoutSuffix, key, isFuture) { - var result = number + ' '; - switch (key) { - case 's': // a few seconds / in a few seconds / a few seconds ago - return (withoutSuffix || isFuture) ? 'pár sekúnd' : 'pár sekundami'; - case 'm': // a minute / in a minute / a minute ago - return withoutSuffix ? 'minúta' : (isFuture ? 'minútu' : 'minútou'); - case 'mm': // 9 minutes / in 9 minutes / 9 minutes ago - if (withoutSuffix || isFuture) { - return result + (plural(number) ? 'minúty' : 'minút'); - } else { - return result + 'minútami'; - } - break; - case 'h': // an hour / in an hour / an hour ago - return withoutSuffix ? 'hodina' : (isFuture ? 'hodinu' : 'hodinou'); - case 'hh': // 9 hours / in 9 hours / 9 hours ago - if (withoutSuffix || isFuture) { - return result + (plural(number) ? 'hodiny' : 'hodín'); - } else { - return result + 'hodinami'; - } - break; - case 'd': // a day / in a day / a day ago - return (withoutSuffix || isFuture) ? 'deň' : 'dňom'; - case 'dd': // 9 days / in 9 days / 9 days ago - if (withoutSuffix || isFuture) { - return result + (plural(number) ? 'dni' : 'dní'); - } else { - return result + 'dňami'; - } - break; - case 'M': // a month / in a month / a month ago - return (withoutSuffix || isFuture) ? 'mesiac' : 'mesiacom'; - case 'MM': // 9 months / in 9 months / 9 months ago - if (withoutSuffix || isFuture) { - return result + (plural(number) ? 'mesiace' : 'mesiacov'); - } else { - return result + 'mesiacmi'; - } - break; - case 'y': // a year / in a year / a year ago - return (withoutSuffix || isFuture) ? 'rok' : 'rokom'; - case 'yy': // 9 years / in 9 years / 9 years ago - if (withoutSuffix || isFuture) { - return result + (plural(number) ? 'roky' : 'rokov'); - } else { - return result + 'rokmi'; - } - break; - } - } - - return moment.defineLocale('sk', { - months : months, - monthsShort : monthsShort, - monthsParse : (function (months, monthsShort) { - var i, _monthsParse = []; - for (i = 0; i < 12; i++) { - // use custom parser to solve problem with July (červenec) - _monthsParse[i] = new RegExp('^' + months[i] + '$|^' + monthsShort[i] + '$', 'i'); - } - return _monthsParse; - }(months, monthsShort)), - weekdays : 'nedeľa_pondelok_utorok_streda_štvrtok_piatok_sobota'.split('_'), - weekdaysShort : 'ne_po_ut_st_št_pi_so'.split('_'), - weekdaysMin : 'ne_po_ut_st_št_pi_so'.split('_'), - longDateFormat : { - LT: 'H:mm', - L : 'DD.MM.YYYY', - LL : 'D. MMMM YYYY', - LLL : 'D. MMMM YYYY LT', - LLLL : 'dddd D. MMMM YYYY LT' - }, - calendar : { - sameDay: '[dnes o] LT', - nextDay: '[zajtra o] LT', - nextWeek: function () { - switch (this.day()) { - case 0: - return '[v nedeľu o] LT'; - case 1: - case 2: - return '[v] dddd [o] LT'; - case 3: - return '[v stredu o] LT'; - case 4: - return '[vo štvrtok o] LT'; - case 5: - return '[v piatok o] LT'; - case 6: - return '[v sobotu o] LT'; - } - }, - lastDay: '[včera o] LT', - lastWeek: function () { - switch (this.day()) { - case 0: - return '[minulú nedeľu o] LT'; - case 1: - case 2: - return '[minulý] dddd [o] LT'; - case 3: - return '[minulú stredu o] LT'; - case 4: - case 5: - return '[minulý] dddd [o] LT'; - case 6: - return '[minulú sobotu o] LT'; - } - }, - sameElse: 'L' - }, - relativeTime : { - future : 'za %s', - past : 'pred %s', - s : translate, - m : translate, - mm : translate, - h : translate, - hh : translate, - d : translate, - dd : translate, - M : translate, - MM : translate, - y : translate, - yy : translate - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : slovenian (sl) -// author : Robert Sedovšek : https://github.com/sedovsek - -(function (factory) { - factory(moment); -}(function (moment) { - function translate(number, withoutSuffix, key) { - var result = number + ' '; - switch (key) { - case 'm': - return withoutSuffix ? 'ena minuta' : 'eno minuto'; - case 'mm': - if (number === 1) { - result += 'minuta'; - } else if (number === 2) { - result += 'minuti'; - } else if (number === 3 || number === 4) { - result += 'minute'; - } else { - result += 'minut'; - } - return result; - case 'h': - return withoutSuffix ? 'ena ura' : 'eno uro'; - case 'hh': - if (number === 1) { - result += 'ura'; - } else if (number === 2) { - result += 'uri'; - } else if (number === 3 || number === 4) { - result += 'ure'; - } else { - result += 'ur'; - } - return result; - case 'dd': - if (number === 1) { - result += 'dan'; - } else { - result += 'dni'; - } - return result; - case 'MM': - if (number === 1) { - result += 'mesec'; - } else if (number === 2) { - result += 'meseca'; - } else if (number === 3 || number === 4) { - result += 'mesece'; - } else { - result += 'mesecev'; - } - return result; - case 'yy': - if (number === 1) { - result += 'leto'; - } else if (number === 2) { - result += 'leti'; - } else if (number === 3 || number === 4) { - result += 'leta'; - } else { - result += 'let'; - } - return result; - } - } - - return moment.defineLocale('sl', { - months : 'januar_februar_marec_april_maj_junij_julij_avgust_september_oktober_november_december'.split('_'), - monthsShort : 'jan._feb._mar._apr._maj._jun._jul._avg._sep._okt._nov._dec.'.split('_'), - weekdays : 'nedelja_ponedeljek_torek_sreda_četrtek_petek_sobota'.split('_'), - weekdaysShort : 'ned._pon._tor._sre._čet._pet._sob.'.split('_'), - weekdaysMin : 'ne_po_to_sr_če_pe_so'.split('_'), - longDateFormat : { - LT : 'H:mm', - L : 'DD. MM. YYYY', - LL : 'D. MMMM YYYY', - LLL : 'D. MMMM YYYY LT', - LLLL : 'dddd, D. MMMM YYYY LT' - }, - calendar : { - sameDay : '[danes ob] LT', - nextDay : '[jutri ob] LT', - - nextWeek : function () { - switch (this.day()) { - case 0: - return '[v] [nedeljo] [ob] LT'; - case 3: - return '[v] [sredo] [ob] LT'; - case 6: - return '[v] [soboto] [ob] LT'; - case 1: - case 2: - case 4: - case 5: - return '[v] dddd [ob] LT'; - } - }, - lastDay : '[včeraj ob] LT', - lastWeek : function () { - switch (this.day()) { - case 0: - case 3: - case 6: - return '[prejšnja] dddd [ob] LT'; - case 1: - case 2: - case 4: - case 5: - return '[prejšnji] dddd [ob] LT'; - } - }, - sameElse : 'L' - }, - relativeTime : { - future : 'čez %s', - past : '%s nazaj', - s : 'nekaj sekund', - m : translate, - mm : translate, - h : translate, - hh : translate, - d : 'en dan', - dd : translate, - M : 'en mesec', - MM : translate, - y : 'eno leto', - yy : translate - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Albanian (sq) -// author : Flakërim Ismani : https://github.com/flakerimi -// author: Menelion Elensúle: https://github.com/Oire (tests) -// author : Oerd Cukalla : https://github.com/oerd (fixes) - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('sq', { - months : 'Janar_Shkurt_Mars_Prill_Maj_Qershor_Korrik_Gusht_Shtator_Tetor_Nëntor_Dhjetor'.split('_'), - monthsShort : 'Jan_Shk_Mar_Pri_Maj_Qer_Kor_Gus_Sht_Tet_Nën_Dhj'.split('_'), - weekdays : 'E Diel_E Hënë_E Martë_E Mërkurë_E Enjte_E Premte_E Shtunë'.split('_'), - weekdaysShort : 'Die_Hën_Mar_Mër_Enj_Pre_Sht'.split('_'), - weekdaysMin : 'D_H_Ma_Më_E_P_Sh'.split('_'), - meridiem : function (hours, minutes, isLower) { - return hours < 12 ? 'PD' : 'MD'; - }, - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd, D MMMM YYYY LT' - }, - calendar : { - sameDay : '[Sot në] LT', - nextDay : '[Nesër në] LT', - nextWeek : 'dddd [në] LT', - lastDay : '[Dje në] LT', - lastWeek : 'dddd [e kaluar në] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'në %s', - past : '%s më parë', - s : 'disa sekonda', - m : 'një minutë', - mm : '%d minuta', - h : 'një orë', - hh : '%d orë', - d : 'një ditë', - dd : '%d ditë', - M : 'një muaj', - MM : '%d muaj', - y : 'një vit', - yy : '%d vite' - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Serbian-cyrillic (sr-cyrl) -// author : Milan Janačković : https://github.com/milan-j - -(function (factory) { - factory(moment); -}(function (moment) { - var translator = { - words: { //Different grammatical cases - m: ['један минут', 'једне минуте'], - mm: ['минут', 'минуте', 'минута'], - h: ['један сат', 'једног сата'], - hh: ['сат', 'сата', 'сати'], - dd: ['дан', 'дана', 'дана'], - MM: ['месец', 'месеца', 'месеци'], - yy: ['година', 'године', 'година'] - }, - correctGrammaticalCase: function (number, wordKey) { - return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]); - }, - translate: function (number, withoutSuffix, key) { - var wordKey = translator.words[key]; - if (key.length === 1) { - return withoutSuffix ? wordKey[0] : wordKey[1]; - } else { - return number + ' ' + translator.correctGrammaticalCase(number, wordKey); - } - } - }; - - return moment.defineLocale('sr-cyrl', { - months: ['јануар', 'фебруар', 'март', 'април', 'мај', 'јун', 'јул', 'август', 'септембар', 'октобар', 'новембар', 'децембар'], - monthsShort: ['јан.', 'феб.', 'мар.', 'апр.', 'мај', 'јун', 'јул', 'авг.', 'сеп.', 'окт.', 'нов.', 'дец.'], - weekdays: ['недеља', 'понедељак', 'уторак', 'среда', 'четвртак', 'петак', 'субота'], - weekdaysShort: ['нед.', 'пон.', 'уто.', 'сре.', 'чет.', 'пет.', 'суб.'], - weekdaysMin: ['не', 'по', 'ут', 'ср', 'че', 'пе', 'су'], - longDateFormat: { - LT: 'H:mm', - L: 'DD. MM. YYYY', - LL: 'D. MMMM YYYY', - LLL: 'D. MMMM YYYY LT', - LLLL: 'dddd, D. MMMM YYYY LT' - }, - calendar: { - sameDay: '[данас у] LT', - nextDay: '[сутра у] LT', - - nextWeek: function () { - switch (this.day()) { - case 0: - return '[у] [недељу] [у] LT'; - case 3: - return '[у] [среду] [у] LT'; - case 6: - return '[у] [суботу] [у] LT'; - case 1: - case 2: - case 4: - case 5: - return '[у] dddd [у] LT'; - } - }, - lastDay : '[јуче у] LT', - lastWeek : function () { - var lastWeekDays = [ - '[прошле] [недеље] [у] LT', - '[прошлог] [понедељка] [у] LT', - '[прошлог] [уторка] [у] LT', - '[прошле] [среде] [у] LT', - '[прошлог] [четвртка] [у] LT', - '[прошлог] [петка] [у] LT', - '[прошле] [суботе] [у] LT' - ]; - return lastWeekDays[this.day()]; - }, - sameElse : 'L' - }, - relativeTime : { - future : 'за %s', - past : 'пре %s', - s : 'неколико секунди', - m : translator.translate, - mm : translator.translate, - h : translator.translate, - hh : translator.translate, - d : 'дан', - dd : translator.translate, - M : 'месец', - MM : translator.translate, - y : 'годину', - yy : translator.translate - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Serbian-latin (sr) -// author : Milan Janačković : https://github.com/milan-j - -(function (factory) { - factory(moment); -}(function (moment) { - var translator = { - words: { //Different grammatical cases - m: ['jedan minut', 'jedne minute'], - mm: ['minut', 'minute', 'minuta'], - h: ['jedan sat', 'jednog sata'], - hh: ['sat', 'sata', 'sati'], - dd: ['dan', 'dana', 'dana'], - MM: ['mesec', 'meseca', 'meseci'], - yy: ['godina', 'godine', 'godina'] - }, - correctGrammaticalCase: function (number, wordKey) { - return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]); - }, - translate: function (number, withoutSuffix, key) { - var wordKey = translator.words[key]; - if (key.length === 1) { - return withoutSuffix ? wordKey[0] : wordKey[1]; - } else { - return number + ' ' + translator.correctGrammaticalCase(number, wordKey); - } - } - }; - - return moment.defineLocale('sr', { - months: ['januar', 'februar', 'mart', 'april', 'maj', 'jun', 'jul', 'avgust', 'septembar', 'oktobar', 'novembar', 'decembar'], - monthsShort: ['jan.', 'feb.', 'mar.', 'apr.', 'maj', 'jun', 'jul', 'avg.', 'sep.', 'okt.', 'nov.', 'dec.'], - weekdays: ['nedelja', 'ponedeljak', 'utorak', 'sreda', 'četvrtak', 'petak', 'subota'], - weekdaysShort: ['ned.', 'pon.', 'uto.', 'sre.', 'čet.', 'pet.', 'sub.'], - weekdaysMin: ['ne', 'po', 'ut', 'sr', 'če', 'pe', 'su'], - longDateFormat: { - LT: 'H:mm', - L: 'DD. MM. YYYY', - LL: 'D. MMMM YYYY', - LLL: 'D. MMMM YYYY LT', - LLLL: 'dddd, D. MMMM YYYY LT' - }, - calendar: { - sameDay: '[danas u] LT', - nextDay: '[sutra u] LT', - - nextWeek: function () { - switch (this.day()) { - case 0: - return '[u] [nedelju] [u] LT'; - case 3: - return '[u] [sredu] [u] LT'; - case 6: - return '[u] [subotu] [u] LT'; - case 1: - case 2: - case 4: - case 5: - return '[u] dddd [u] LT'; - } - }, - lastDay : '[juče u] LT', - lastWeek : function () { - var lastWeekDays = [ - '[prošle] [nedelje] [u] LT', - '[prošlog] [ponedeljka] [u] LT', - '[prošlog] [utorka] [u] LT', - '[prošle] [srede] [u] LT', - '[prošlog] [četvrtka] [u] LT', - '[prošlog] [petka] [u] LT', - '[prošle] [subote] [u] LT' - ]; - return lastWeekDays[this.day()]; - }, - sameElse : 'L' - }, - relativeTime : { - future : 'za %s', - past : 'pre %s', - s : 'nekoliko sekundi', - m : translator.translate, - mm : translator.translate, - h : translator.translate, - hh : translator.translate, - d : 'dan', - dd : translator.translate, - M : 'mesec', - MM : translator.translate, - y : 'godinu', - yy : translator.translate - }, - ordinal : '%d.', - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : swedish (sv) -// author : Jens Alm : https://github.com/ulmus - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('sv', { - months : 'januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december'.split('_'), - monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'), - weekdays : 'söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag'.split('_'), - weekdaysShort : 'sön_mån_tis_ons_tor_fre_lör'.split('_'), - weekdaysMin : 'sö_må_ti_on_to_fr_lö'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'YYYY-MM-DD', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd D MMMM YYYY LT' - }, - calendar : { - sameDay: '[Idag] LT', - nextDay: '[Imorgon] LT', - lastDay: '[Igår] LT', - nextWeek: 'dddd LT', - lastWeek: '[Förra] dddd[en] LT', - sameElse: 'L' - }, - relativeTime : { - future : 'om %s', - past : 'för %s sedan', - s : 'några sekunder', - m : 'en minut', - mm : '%d minuter', - h : 'en timme', - hh : '%d timmar', - d : 'en dag', - dd : '%d dagar', - M : 'en månad', - MM : '%d månader', - y : 'ett år', - yy : '%d år' - }, - ordinal : function (number) { - var b = number % 10, - output = (~~(number % 100 / 10) === 1) ? 'e' : - (b === 1) ? 'a' : - (b === 2) ? 'a' : - (b === 3) ? 'e' : 'e'; - return number + output; - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : tamil (ta) -// author : Arjunkumar Krishnamoorthy : https://github.com/tk120404 - -(function (factory) { - factory(moment); -}(function (moment) { - /*var symbolMap = { - '1': '௧', - '2': '௨', - '3': '௩', - '4': '௪', - '5': '௫', - '6': '௬', - '7': '௭', - '8': '௮', - '9': '௯', - '0': '௦' - }, - numberMap = { - '௧': '1', - '௨': '2', - '௩': '3', - '௪': '4', - '௫': '5', - '௬': '6', - '௭': '7', - '௮': '8', - '௯': '9', - '௦': '0' - }; */ - - return moment.defineLocale('ta', { - months : 'ஜனவரி_பிப்ரவரி_மார்ச்_ஏப்ரல்_மே_ஜூன்_ஜூலை_ஆகஸ்ட்_செப்டெம்பர்_அக்டோபர்_நவம்பர்_டிசம்பர்'.split('_'), - monthsShort : 'ஜனவரி_பிப்ரவரி_மார்ச்_ஏப்ரல்_மே_ஜூன்_ஜூலை_ஆகஸ்ட்_செப்டெம்பர்_அக்டோபர்_நவம்பர்_டிசம்பர்'.split('_'), - weekdays : 'ஞாயிற்றுக்கிழமை_திங்கட்கிழமை_செவ்வாய்கிழமை_புதன்கிழமை_வியாழக்கிழமை_வெள்ளிக்கிழமை_சனிக்கிழமை'.split('_'), - weekdaysShort : 'ஞாயிறு_திங்கள்_செவ்வாய்_புதன்_வியாழன்_வெள்ளி_சனி'.split('_'), - weekdaysMin : 'ஞா_தி_செ_பு_வி_வெ_ச'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY, LT', - LLLL : 'dddd, D MMMM YYYY, LT' - }, - calendar : { - sameDay : '[இன்று] LT', - nextDay : '[நாளை] LT', - nextWeek : 'dddd, LT', - lastDay : '[நேற்று] LT', - lastWeek : '[கடந்த வாரம்] dddd, LT', - sameElse : 'L' - }, - relativeTime : { - future : '%s இல்', - past : '%s முன்', - s : 'ஒரு சில விநாடிகள்', - m : 'ஒரு நிமிடம்', - mm : '%d நிமிடங்கள்', - h : 'ஒரு மணி நேரம்', - hh : '%d மணி நேரம்', - d : 'ஒரு நாள்', - dd : '%d நாட்கள்', - M : 'ஒரு மாதம்', - MM : '%d மாதங்கள்', - y : 'ஒரு வருடம்', - yy : '%d ஆண்டுகள்' - }, -/* preparse: function (string) { - return string.replace(/[௧௨௩௪௫௬௭௮௯௦]/g, function (match) { - return numberMap[match]; - }); - }, - postformat: function (string) { - return string.replace(/\d/g, function (match) { - return symbolMap[match]; - }); - },*/ - ordinal : function (number) { - return number + 'வது'; - }, - - - // refer http://ta.wikipedia.org/s/1er1 - - meridiem : function (hour, minute, isLower) { - if (hour >= 6 && hour <= 10) { - return ' காலை'; - } else if (hour >= 10 && hour <= 14) { - return ' நண்பகல்'; - } else if (hour >= 14 && hour <= 18) { - return ' எற்பாடு'; - } else if (hour >= 18 && hour <= 20) { - return ' மாலை'; - } else if (hour >= 20 && hour <= 24) { - return ' இரவு'; - } else if (hour >= 0 && hour <= 6) { - return ' வைகறை'; - } - }, - week : { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : thai (th) -// author : Kridsada Thanabulpong : https://github.com/sirn - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('th', { - months : 'มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม'.split('_'), - monthsShort : 'มกรา_กุมภา_มีนา_เมษา_พฤษภา_มิถุนา_กรกฎา_สิงหา_กันยา_ตุลา_พฤศจิกา_ธันวา'.split('_'), - weekdays : 'อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์'.split('_'), - weekdaysShort : 'อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์'.split('_'), // yes, three characters difference - weekdaysMin : 'อา._จ._อ._พ._พฤ._ศ._ส.'.split('_'), - longDateFormat : { - LT : 'H นาฬิกา m นาที', - L : 'YYYY/MM/DD', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY เวลา LT', - LLLL : 'วันddddที่ D MMMM YYYY เวลา LT' - }, - meridiem : function (hour, minute, isLower) { - if (hour < 12) { - return 'ก่อนเที่ยง'; - } else { - return 'หลังเที่ยง'; - } - }, - calendar : { - sameDay : '[วันนี้ เวลา] LT', - nextDay : '[พรุ่งนี้ เวลา] LT', - nextWeek : 'dddd[หน้า เวลา] LT', - lastDay : '[เมื่อวานนี้ เวลา] LT', - lastWeek : '[วัน]dddd[ที่แล้ว เวลา] LT', - sameElse : 'L' - }, - relativeTime : { - future : 'อีก %s', - past : '%sที่แล้ว', - s : 'ไม่กี่วินาที', - m : '1 นาที', - mm : '%d นาที', - h : '1 ชั่วโมง', - hh : '%d ชั่วโมง', - d : '1 วัน', - dd : '%d วัน', - M : '1 เดือน', - MM : '%d เดือน', - y : '1 ปี', - yy : '%d ปี' - } - }); -})); -// moment.js locale configuration -// locale : Tagalog/Filipino (tl-ph) -// author : Dan Hagman - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('tl-ph', { - months : 'Enero_Pebrero_Marso_Abril_Mayo_Hunyo_Hulyo_Agosto_Setyembre_Oktubre_Nobyembre_Disyembre'.split('_'), - monthsShort : 'Ene_Peb_Mar_Abr_May_Hun_Hul_Ago_Set_Okt_Nob_Dis'.split('_'), - weekdays : 'Linggo_Lunes_Martes_Miyerkules_Huwebes_Biyernes_Sabado'.split('_'), - weekdaysShort : 'Lin_Lun_Mar_Miy_Huw_Biy_Sab'.split('_'), - weekdaysMin : 'Li_Lu_Ma_Mi_Hu_Bi_Sab'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'MM/D/YYYY', - LL : 'MMMM D, YYYY', - LLL : 'MMMM D, YYYY LT', - LLLL : 'dddd, MMMM DD, YYYY LT' - }, - calendar : { - sameDay: '[Ngayon sa] LT', - nextDay: '[Bukas sa] LT', - nextWeek: 'dddd [sa] LT', - lastDay: '[Kahapon sa] LT', - lastWeek: 'dddd [huling linggo] LT', - sameElse: 'L' - }, - relativeTime : { - future : 'sa loob ng %s', - past : '%s ang nakalipas', - s : 'ilang segundo', - m : 'isang minuto', - mm : '%d minuto', - h : 'isang oras', - hh : '%d oras', - d : 'isang araw', - dd : '%d araw', - M : 'isang buwan', - MM : '%d buwan', - y : 'isang taon', - yy : '%d taon' - }, - ordinal : function (number) { - return number; - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : turkish (tr) -// authors : Erhan Gundogan : https://github.com/erhangundogan, -// Burak Yiğit Kaya: https://github.com/BYK - -(function (factory) { - factory(moment); -}(function (moment) { - var suffixes = { - 1: '\'inci', - 5: '\'inci', - 8: '\'inci', - 70: '\'inci', - 80: '\'inci', - - 2: '\'nci', - 7: '\'nci', - 20: '\'nci', - 50: '\'nci', - - 3: '\'üncü', - 4: '\'üncü', - 100: '\'üncü', - - 6: '\'ncı', - - 9: '\'uncu', - 10: '\'uncu', - 30: '\'uncu', - - 60: '\'ıncı', - 90: '\'ıncı' - }; - - return moment.defineLocale('tr', { - months : 'Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık'.split('_'), - monthsShort : 'Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara'.split('_'), - weekdays : 'Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi'.split('_'), - weekdaysShort : 'Paz_Pts_Sal_Çar_Per_Cum_Cts'.split('_'), - weekdaysMin : 'Pz_Pt_Sa_Ça_Pe_Cu_Ct'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD.MM.YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd, D MMMM YYYY LT' - }, - calendar : { - sameDay : '[bugün saat] LT', - nextDay : '[yarın saat] LT', - nextWeek : '[haftaya] dddd [saat] LT', - lastDay : '[dün] LT', - lastWeek : '[geçen hafta] dddd [saat] LT', - sameElse : 'L' - }, - relativeTime : { - future : '%s sonra', - past : '%s önce', - s : 'birkaç saniye', - m : 'bir dakika', - mm : '%d dakika', - h : 'bir saat', - hh : '%d saat', - d : 'bir gün', - dd : '%d gün', - M : 'bir ay', - MM : '%d ay', - y : 'bir yıl', - yy : '%d yıl' - }, - ordinal : function (number) { - if (number === 0) { // special case for zero - return number + '\'ıncı'; - } - var a = number % 10, - b = number % 100 - a, - c = number >= 100 ? 100 : null; - - return number + (suffixes[a] || suffixes[b] || suffixes[c]); - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Morocco Central Atlas Tamaziɣt in Latin (tzm-latn) -// author : Abdel Said : https://github.com/abdelsaid - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('tzm-latn', { - months : 'innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir'.split('_'), - monthsShort : 'innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir'.split('_'), - weekdays : 'asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas'.split('_'), - weekdaysShort : 'asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas'.split('_'), - weekdaysMin : 'asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd D MMMM YYYY LT' - }, - calendar : { - sameDay: '[asdkh g] LT', - nextDay: '[aska g] LT', - nextWeek: 'dddd [g] LT', - lastDay: '[assant g] LT', - lastWeek: 'dddd [g] LT', - sameElse: 'L' - }, - relativeTime : { - future : 'dadkh s yan %s', - past : 'yan %s', - s : 'imik', - m : 'minuḍ', - mm : '%d minuḍ', - h : 'saɛa', - hh : '%d tassaɛin', - d : 'ass', - dd : '%d ossan', - M : 'ayowr', - MM : '%d iyyirn', - y : 'asgas', - yy : '%d isgasn' - }, - week : { - dow : 6, // Saturday is the first day of the week. - doy : 12 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : Morocco Central Atlas Tamaziɣt (tzm) -// author : Abdel Said : https://github.com/abdelsaid - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('tzm', { - months : 'ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ'.split('_'), - monthsShort : 'ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ'.split('_'), - weekdays : 'ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ'.split('_'), - weekdaysShort : 'ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ'.split('_'), - weekdaysMin : 'ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'dddd D MMMM YYYY LT' - }, - calendar : { - sameDay: '[ⴰⵙⴷⵅ ⴴ] LT', - nextDay: '[ⴰⵙⴽⴰ ⴴ] LT', - nextWeek: 'dddd [ⴴ] LT', - lastDay: '[ⴰⵚⴰⵏⵜ ⴴ] LT', - lastWeek: 'dddd [ⴴ] LT', - sameElse: 'L' - }, - relativeTime : { - future : 'ⴷⴰⴷⵅ ⵙ ⵢⴰⵏ %s', - past : 'ⵢⴰⵏ %s', - s : 'ⵉⵎⵉⴽ', - m : 'ⵎⵉⵏⵓⴺ', - mm : '%d ⵎⵉⵏⵓⴺ', - h : 'ⵙⴰⵄⴰ', - hh : '%d ⵜⴰⵙⵙⴰⵄⵉⵏ', - d : 'ⴰⵙⵙ', - dd : '%d oⵙⵙⴰⵏ', - M : 'ⴰⵢoⵓⵔ', - MM : '%d ⵉⵢⵢⵉⵔⵏ', - y : 'ⴰⵙⴳⴰⵙ', - yy : '%d ⵉⵙⴳⴰⵙⵏ' - }, - week : { - dow : 6, // Saturday is the first day of the week. - doy : 12 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : ukrainian (uk) -// author : zemlanin : https://github.com/zemlanin -// Author : Menelion Elensúle : https://github.com/Oire - -(function (factory) { - factory(moment); -}(function (moment) { - function plural(word, num) { - var forms = word.split('_'); - return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]); - } - - function relativeTimeWithPlural(number, withoutSuffix, key) { - var format = { - 'mm': 'хвилина_хвилини_хвилин', - 'hh': 'година_години_годин', - 'dd': 'день_дні_днів', - 'MM': 'місяць_місяці_місяців', - 'yy': 'рік_роки_років' - }; - if (key === 'm') { - return withoutSuffix ? 'хвилина' : 'хвилину'; - } - else if (key === 'h') { - return withoutSuffix ? 'година' : 'годину'; - } - else { - return number + ' ' + plural(format[key], +number); - } - } - - function monthsCaseReplace(m, format) { - var months = { - 'nominative': 'січень_лютий_березень_квітень_травень_червень_липень_серпень_вересень_жовтень_листопад_грудень'.split('_'), - 'accusative': 'січня_лютого_березня_квітня_травня_червня_липня_серпня_вересня_жовтня_листопада_грудня'.split('_') - }, - - nounCase = (/D[oD]? *MMMM?/).test(format) ? - 'accusative' : - 'nominative'; - - return months[nounCase][m.month()]; - } - - function weekdaysCaseReplace(m, format) { - var weekdays = { - 'nominative': 'неділя_понеділок_вівторок_середа_четвер_п’ятниця_субота'.split('_'), - 'accusative': 'неділю_понеділок_вівторок_середу_четвер_п’ятницю_суботу'.split('_'), - 'genitive': 'неділі_понеділка_вівторка_середи_четверга_п’ятниці_суботи'.split('_') - }, - - nounCase = (/(\[[ВвУу]\]) ?dddd/).test(format) ? - 'accusative' : - ((/\[?(?:минулої|наступної)? ?\] ?dddd/).test(format) ? - 'genitive' : - 'nominative'); - - return weekdays[nounCase][m.day()]; - } - - function processHoursFunction(str) { - return function () { - return str + 'о' + (this.hours() === 11 ? 'б' : '') + '] LT'; - }; - } - - return moment.defineLocale('uk', { - months : monthsCaseReplace, - monthsShort : 'січ_лют_бер_квіт_трав_черв_лип_серп_вер_жовт_лист_груд'.split('_'), - weekdays : weekdaysCaseReplace, - weekdaysShort : 'нд_пн_вт_ср_чт_пт_сб'.split('_'), - weekdaysMin : 'нд_пн_вт_ср_чт_пт_сб'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD.MM.YYYY', - LL : 'D MMMM YYYY р.', - LLL : 'D MMMM YYYY р., LT', - LLLL : 'dddd, D MMMM YYYY р., LT' - }, - calendar : { - sameDay: processHoursFunction('[Сьогодні '), - nextDay: processHoursFunction('[Завтра '), - lastDay: processHoursFunction('[Вчора '), - nextWeek: processHoursFunction('[У] dddd ['), - lastWeek: function () { - switch (this.day()) { - case 0: - case 3: - case 5: - case 6: - return processHoursFunction('[Минулої] dddd [').call(this); - case 1: - case 2: - case 4: - return processHoursFunction('[Минулого] dddd [').call(this); - } - }, - sameElse: 'L' - }, - relativeTime : { - future : 'за %s', - past : '%s тому', - s : 'декілька секунд', - m : relativeTimeWithPlural, - mm : relativeTimeWithPlural, - h : 'годину', - hh : relativeTimeWithPlural, - d : 'день', - dd : relativeTimeWithPlural, - M : 'місяць', - MM : relativeTimeWithPlural, - y : 'рік', - yy : relativeTimeWithPlural - }, - - // M. E.: those two are virtually unused but a user might want to implement them for his/her website for some reason - - meridiem : function (hour, minute, isLower) { - if (hour < 4) { - return 'ночі'; - } else if (hour < 12) { - return 'ранку'; - } else if (hour < 17) { - return 'дня'; - } else { - return 'вечора'; - } - }, - - ordinal: function (number, period) { - switch (period) { - case 'M': - case 'd': - case 'DDD': - case 'w': - case 'W': - return number + '-й'; - case 'D': - return number + '-го'; - default: - return number; - } - }, - - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 1st is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : uzbek (uz) -// author : Sardor Muminov : https://github.com/muminoff - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('uz', { - months : 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_'), - monthsShort : 'янв_фев_мар_апр_май_июн_июл_авг_сен_окт_ноя_дек'.split('_'), - weekdays : 'Якшанба_Душанба_Сешанба_Чоршанба_Пайшанба_Жума_Шанба'.split('_'), - weekdaysShort : 'Якш_Душ_Сеш_Чор_Пай_Жум_Шан'.split('_'), - weekdaysMin : 'Як_Ду_Се_Чо_Па_Жу_Ша'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM YYYY', - LLL : 'D MMMM YYYY LT', - LLLL : 'D MMMM YYYY, dddd LT' - }, - calendar : { - sameDay : '[Бугун соат] LT [да]', - nextDay : '[Эртага] LT [да]', - nextWeek : 'dddd [куни соат] LT [да]', - lastDay : '[Кеча соат] LT [да]', - lastWeek : '[Утган] dddd [куни соат] LT [да]', - sameElse : 'L' - }, - relativeTime : { - future : 'Якин %s ичида', - past : 'Бир неча %s олдин', - s : 'фурсат', - m : 'бир дакика', - mm : '%d дакика', - h : 'бир соат', - hh : '%d соат', - d : 'бир кун', - dd : '%d кун', - M : 'бир ой', - MM : '%d ой', - y : 'бир йил', - yy : '%d йил' - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 7 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : vietnamese (vi) -// author : Bang Nguyen : https://github.com/bangnk - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('vi', { - months : 'tháng 1_tháng 2_tháng 3_tháng 4_tháng 5_tháng 6_tháng 7_tháng 8_tháng 9_tháng 10_tháng 11_tháng 12'.split('_'), - monthsShort : 'Th01_Th02_Th03_Th04_Th05_Th06_Th07_Th08_Th09_Th10_Th11_Th12'.split('_'), - weekdays : 'chủ nhật_thứ hai_thứ ba_thứ tư_thứ năm_thứ sáu_thứ bảy'.split('_'), - weekdaysShort : 'CN_T2_T3_T4_T5_T6_T7'.split('_'), - weekdaysMin : 'CN_T2_T3_T4_T5_T6_T7'.split('_'), - longDateFormat : { - LT : 'HH:mm', - L : 'DD/MM/YYYY', - LL : 'D MMMM [năm] YYYY', - LLL : 'D MMMM [năm] YYYY LT', - LLLL : 'dddd, D MMMM [năm] YYYY LT', - l : 'DD/M/YYYY', - ll : 'D MMM YYYY', - lll : 'D MMM YYYY LT', - llll : 'ddd, D MMM YYYY LT' - }, - calendar : { - sameDay: '[Hôm nay lúc] LT', - nextDay: '[Ngày mai lúc] LT', - nextWeek: 'dddd [tuần tới lúc] LT', - lastDay: '[Hôm qua lúc] LT', - lastWeek: 'dddd [tuần rồi lúc] LT', - sameElse: 'L' - }, - relativeTime : { - future : '%s tới', - past : '%s trước', - s : 'vài giây', - m : 'một phút', - mm : '%d phút', - h : 'một giờ', - hh : '%d giờ', - d : 'một ngày', - dd : '%d ngày', - M : 'một tháng', - MM : '%d tháng', - y : 'một năm', - yy : '%d năm' - }, - ordinal : function (number) { - return number; - }, - week : { - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : chinese (zh-cn) -// author : suupic : https://github.com/suupic -// author : Zeno Zeng : https://github.com/zenozeng - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('zh-cn', { - months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'), - monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), - weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'), - weekdaysShort : '周日_周一_周二_周三_周四_周五_周六'.split('_'), - weekdaysMin : '日_一_二_三_四_五_六'.split('_'), - longDateFormat : { - LT : 'Ah点mm', - L : 'YYYY-MM-DD', - LL : 'YYYY年MMMD日', - LLL : 'YYYY年MMMD日LT', - LLLL : 'YYYY年MMMD日ddddLT', - l : 'YYYY-MM-DD', - ll : 'YYYY年MMMD日', - lll : 'YYYY年MMMD日LT', - llll : 'YYYY年MMMD日ddddLT' - }, - meridiem : function (hour, minute, isLower) { - var hm = hour * 100 + minute; - if (hm < 600) { - return '凌晨'; - } else if (hm < 900) { - return '早上'; - } else if (hm < 1130) { - return '上午'; - } else if (hm < 1230) { - return '中午'; - } else if (hm < 1800) { - return '下午'; - } else { - return '晚上'; - } - }, - calendar : { - sameDay : function () { - return this.minutes() === 0 ? '[今天]Ah[点整]' : '[今天]LT'; - }, - nextDay : function () { - return this.minutes() === 0 ? '[明天]Ah[点整]' : '[明天]LT'; - }, - lastDay : function () { - return this.minutes() === 0 ? '[昨天]Ah[点整]' : '[昨天]LT'; - }, - nextWeek : function () { - var startOfWeek, prefix; - startOfWeek = moment().startOf('week'); - prefix = this.unix() - startOfWeek.unix() >= 7 * 24 * 3600 ? '[下]' : '[本]'; - return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm'; - }, - lastWeek : function () { - var startOfWeek, prefix; - startOfWeek = moment().startOf('week'); - prefix = this.unix() < startOfWeek.unix() ? '[上]' : '[本]'; - return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm'; - }, - sameElse : 'LL' - }, - ordinal : function (number, period) { - switch (period) { - case 'd': - case 'D': - case 'DDD': - return number + '日'; - case 'M': - return number + '月'; - case 'w': - case 'W': - return number + '周'; - default: - return number; - } - }, - relativeTime : { - future : '%s内', - past : '%s前', - s : '几秒', - m : '1分钟', - mm : '%d分钟', - h : '1小时', - hh : '%d小时', - d : '1天', - dd : '%d天', - M : '1个月', - MM : '%d个月', - y : '1年', - yy : '%d年' - }, - week : { - // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效 - dow : 1, // Monday is the first day of the week. - doy : 4 // The week that contains Jan 4th is the first week of the year. - } - }); -})); -// moment.js locale configuration -// locale : traditional chinese (zh-tw) -// author : Ben : https://github.com/ben-lin - -(function (factory) { - factory(moment); -}(function (moment) { - return moment.defineLocale('zh-tw', { - months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'), - monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), - weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'), - weekdaysShort : '週日_週一_週二_週三_週四_週五_週六'.split('_'), - weekdaysMin : '日_一_二_三_四_五_六'.split('_'), - longDateFormat : { - LT : 'Ah點mm', - L : 'YYYY年MMMD日', - LL : 'YYYY年MMMD日', - LLL : 'YYYY年MMMD日LT', - LLLL : 'YYYY年MMMD日ddddLT', - l : 'YYYY年MMMD日', - ll : 'YYYY年MMMD日', - lll : 'YYYY年MMMD日LT', - llll : 'YYYY年MMMD日ddddLT' - }, - meridiem : function (hour, minute, isLower) { - var hm = hour * 100 + minute; - if (hm < 900) { - return '早上'; - } else if (hm < 1130) { - return '上午'; - } else if (hm < 1230) { - return '中午'; - } else if (hm < 1800) { - return '下午'; - } else { - return '晚上'; - } - }, - calendar : { - sameDay : '[今天]LT', - nextDay : '[明天]LT', - nextWeek : '[下]ddddLT', - lastDay : '[昨天]LT', - lastWeek : '[上]ddddLT', - sameElse : 'L' - }, - ordinal : function (number, period) { - switch (period) { - case 'd' : - case 'D' : - case 'DDD' : - return number + '日'; - case 'M' : - return number + '月'; - case 'w' : - case 'W' : - return number + '週'; - default : - return number; - } - }, - relativeTime : { - future : '%s內', - past : '%s前', - s : '幾秒', - m : '一分鐘', - mm : '%d分鐘', - h : '一小時', - hh : '%d小時', - d : '一天', - dd : '%d天', - M : '一個月', - MM : '%d個月', - y : '一年', - yy : '%d年' - } - }); -})); - - moment.locale('en'); - - - /************************************ - Exposing Moment - ************************************/ - - function makeGlobal(shouldDeprecate) { - /*global ender:false */ - if (typeof ender !== 'undefined') { - return; - } - oldGlobalMoment = globalScope.moment; - if (shouldDeprecate) { - globalScope.moment = deprecate( - 'Accessing Moment through the global scope is ' + - 'deprecated, and will be removed in an upcoming ' + - 'release.', - moment); - } else { - globalScope.moment = moment; - } - } - - // CommonJS module is defined - if (hasModule) { - module.exports = moment; - } else if (typeof define === 'function' && define.amd) { - define('moment', function (require, exports, module) { - if (module.config && module.config() && module.config().noGlobal === true) { - // release the global variable - globalScope.moment = oldGlobalMoment; - } - - return moment; - }); - makeGlobal(true); - } else { - makeGlobal(); - } -}).call(this); diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg index e8bc1f8300..97fc105261 100644 Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg and b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg differ 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 0692a10e90..5dd7d266df 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 @@ -64,6 +64,7 @@ angular.module("umbraco.directives") await.push(stylesheetResource.getRulesByName(stylesheet).then(function (rules) { angular.forEach(rules, function (rule) { var r = {}; + var split = ""; r.title = rule.name; if (rule.selector[0] === ".") { r.inline = "span"; @@ -74,6 +75,14 @@ angular.module("umbraco.directives") // since only one element can have one id. r.inline = "span"; r.attributes = { id: rule.selector.substring(1) }; + }else if (rule.selector[0] !== "." && rule.selector.indexOf(".") > -1) { + split = rule.selector.split("."); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " "); + }else if (rule.selector[0] !== "#" && rule.selector.indexOf("#") > -1) { + split = rule.selector.split("#"); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1); }else { r.block = rule.selector; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js index 52271961a7..0a69ef5340 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js @@ -1,5 +1,12 @@ angular.module("umbraco.directives") + /** + * @ngdoc directive + * @name umbraco.directives.directive:localize + * @restrict EA + * @function + * @description Localize directive + **/ .directive('localize', function ($log, localizationService) { return { restrict: 'E', @@ -40,4 +47,4 @@ angular.module("umbraco.directives") } }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index a3b2fab00f..4ba4cf96bb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -43,7 +43,6 @@ function treeSearchBox(localizationService, searchService, $q) { //a canceler exists, so perform the cancelation operation and reset if (canceler) { - console.log("CANCELED!"); canceler.resolve(); canceler = $q.defer(); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js new file mode 100644 index 0000000000..ba34a752ed --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js @@ -0,0 +1,72 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbAvatar +@restrict E +@scope + +@description +Use this directive to render an avatar. + +

Markup example

+
+	
+ + + + +
+
+ +

Controller example

+
+	(function () {
+		"use strict";
+
+		function Controller() {
+
+            var vm = this;
+
+            vm.avatar = [
+                { value: "assets/logo.png" },
+                { value: "assets/logo@2x.png" },
+                { value: "assets/logo@3x.png" }
+            ];
+
+        }
+
+		angular.module("umbraco").controller("My.Controller", Controller);
+
+	})();
+
+ +@param {string} size (attribute): The size of the avatar (xs, s, m, l, xl). +@param {string} img-src (attribute): The image source to the avatar. +@param {string} img-srcset (atribute): Reponsive support for the image source. +**/ + +(function() { + 'use strict'; + + function AvatarDirective() { + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-avatar.html', + scope: { + size: "@", + imgSrc: "@", + imgSrcset: "@" + } + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbAvatar', AvatarDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js new file mode 100644 index 0000000000..19a33a8351 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js @@ -0,0 +1,150 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbLightbox +@restrict E +@scope + +@description +

Use this directive to open a gallery in a lightbox overlay.

+ +

Markup example

+
+    
+ + + + + + +
+
+ +

Controller example

+
+    (function () {
+
+        "use strict";
+
+        function Controller() {
+
+            var vm = this;
+
+            vm.images = [
+                {
+                    "source": "linkToImage"
+                },
+                {
+                    "source": "linkToImage"
+                }
+            ]
+
+            vm.openLightbox = openLightbox;
+            vm.closeLightbox = closeLightbox;
+
+            function openLightbox(itemIndex, items) {
+                vm.lightbox = {
+                    show: true,
+                    items: items,
+                    activeIndex: itemIndex
+                };
+            }
+
+            function closeLightbox() {
+                vm.lightbox.show = false;
+                vm.lightbox = null;
+            }
+
+        }
+
+        angular.module("umbraco").controller("My.Controller", Controller);
+    })();
+
+ +@param {array} items Array of gallery items. +@param {callback} onClose Callback when the lightbox is closed. +@param {number} activeItemIndex Index of active item. +**/ + + +(function() { + 'use strict'; + + function LightboxDirective() { + + function link(scope, el, attr, ctrl) { + + + function activate() { + + var eventBindings = []; + + el.appendTo("body"); + + // clean up + scope.$on('$destroy', function() { + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); + } + }); + } + + scope.next = function() { + + var nextItemIndex = scope.activeItemIndex + 1; + + if( nextItemIndex < scope.items.length) { + scope.items[scope.activeItemIndex].active = false; + scope.items[nextItemIndex].active = true; + scope.activeItemIndex = nextItemIndex; + } + }; + + scope.prev = function() { + + var prevItemIndex = scope.activeItemIndex - 1; + + if( prevItemIndex >= 0) { + scope.items[scope.activeItemIndex].active = false; + scope.items[prevItemIndex].active = true; + scope.activeItemIndex = prevItemIndex; + } + + }; + + scope.close = function() { + if(scope.onClose) { + scope.onClose(); + } + }; + + activate(); + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-lightbox.html', + scope: { + items: '=', + onClose: "=", + activeItemIndex: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbLightbox', LightboxDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewsettings.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewsettings.directive.js index ae9da02237..f21f7b8d3d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewsettings.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblistviewsettings.directive.js @@ -1,7 +1,7 @@ (function() { 'use strict'; - function ListViewSettingsDirective(contentTypeResource, dataTypeResource, dataTypeHelper) { + function ListViewSettingsDirective(contentTypeResource, dataTypeResource, dataTypeHelper, listViewPrevalueHelper) { function link(scope, el, attr, ctrl) { @@ -20,6 +20,7 @@ scope.dataType = dataType; + listViewPrevalueHelper.setPrevalues(dataType.preValues); scope.customListViewCreated = checkForCustomListView(); }); 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 7d2da34988..e9e7395761 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 @@ -138,6 +138,22 @@ Use this directive to generate a thumbnail grid of media items. if (!item.isFolder) { item.thumbnail = mediaHelper.resolveFile(item, true); item.image = mediaHelper.resolveFile(item, false); + + var fileProp = _.find(item.properties, function (v) { + return (v.alias === "umbracoFile"); + }); + + if (fileProp && fileProp.value) { + item.file = fileProp.value; + } + + var extensionProp = _.find(item.properties, function (v) { + return (v.alias === "umbracoExtension"); + }); + + if (extensionProp && extensionProp.value) { + item.extension = extensionProp.value; + } } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbprogressbar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbprogressbar.directive.js new file mode 100644 index 0000000000..77bab9f023 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbprogressbar.directive.js @@ -0,0 +1,41 @@ + +/** +@ngdoc directive +@name umbraco.directives.directive:umbProgressBar +@restrict E +@scope + +@description +Use this directive to generate a progress bar. + +

Markup example

+
+    
+    
+
+ +@param {number} percentage (attribute): The progress in percentage. +**/ + +(function() { + 'use strict'; + + function ProgressBarDirective() { + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-progress-bar.html', + scope: { + percentage: "@" + } + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbProgressBar', ProgressBarDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/nodirtycheck.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/nodirtycheck.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/umbsetdirtyonchange.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/umbsetdirtyonchange.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valCustom.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valCustom.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valCustom.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valCustom.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valHighlight.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valHighlight.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js index 2afd75eb29..9182441f8b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valHighlight.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js @@ -1,28 +1,28 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:valHighlight -* @restrict A -* @description Used on input fields when you want to signal that they are in error, this will highlight the item for 1 second -**/ -function valHighlight($timeout) { - return { - restrict: "A", - link: function (scope, element, attrs, ctrl) { - - attrs.$observe("valHighlight", function (newVal) { - if (newVal === "true") { - element.addClass("highlight-error"); - $timeout(function () { - //set the bound scope property to false - scope[attrs.valHighlight] = false; - }, 1000); - } - else { - element.removeClass("highlight-error"); - } - }); - - } - }; -} -angular.module('umbraco.directives.validation').directive("valHighlight", valHighlight); +/** +* @ngdoc directive +* @name umbraco.directives.directive:valHighlight +* @restrict A +* @description Used on input fields when you want to signal that they are in error, this will highlight the item for 1 second +**/ +function valHighlight($timeout) { + return { + restrict: "A", + link: function (scope, element, attrs, ctrl) { + + attrs.$observe("valHighlight", function (newVal) { + if (newVal === "true") { + element.addClass("highlight-error"); + $timeout(function () { + //set the bound scope property to false + scope[attrs.valHighlight] = false; + }, 1000); + } + else { + element.removeClass("highlight-error"); + } + }); + + } + }; +} +angular.module('umbraco.directives.validation').directive("valHighlight", valHighlight); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valcompare.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valcompare.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js index 31595273de..1a36dcc24f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valcompare.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js @@ -1,24 +1,24 @@ -angular.module('umbraco.directives.validation') - .directive('valCompare',function () { - return { - require: "ngModel", - link: function (scope, elem, attrs, ctrl) { - - //TODO: Pretty sure this should be done using a requires ^form in the directive declaration - var otherInput = elem.inheritedData("$formController")[attrs.valCompare]; - - ctrl.$parsers.push(function(value) { - if(value === otherInput.$viewValue) { - ctrl.$setValidity("valCompare", true); - return value; - } - ctrl.$setValidity("valCompare", false); - }); - - otherInput.$parsers.push(function(value) { - ctrl.$setValidity("valCompare", value === ctrl.$viewValue); - return value; - }); - } - }; +angular.module('umbraco.directives.validation') + .directive('valCompare',function () { + return { + require: "ngModel", + link: function (scope, elem, attrs, ctrl) { + + //TODO: Pretty sure this should be done using a requires ^form in the directive declaration + var otherInput = elem.inheritedData("$formController")[attrs.valCompare]; + + ctrl.$parsers.push(function(value) { + if(value === otherInput.$viewValue) { + ctrl.$setValidity("valCompare", true); + return value; + } + ctrl.$setValidity("valCompare", false); + }); + + otherInput.$parsers.push(function(value) { + ctrl.$setValidity("valCompare", value === ctrl.$viewValue); + return value; + }); + } + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valemail.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js similarity index 66% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valemail.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js index 1e81d8edec..8574d01f5a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valemail.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valemail.directive.js @@ -29,6 +29,16 @@ function valEmail(valEmailExpression) { } }; + //if there is an attribute: type="email" then we need to remove those formatters and parsers + if (attrs.type === "email") { + //we need to remove the existing parsers = the default angular one which is created by + // type="email", but this has a regex issue, so we'll remove that and add our custom one + ctrl.$parsers.pop(); + //we also need to remove the existing formatter - the default angular one will not render + // what it thinks is an invalid email address, so it will just be blank + ctrl.$formatters.pop(); + } + ctrl.$parsers.push(patternValidator); } }; @@ -36,7 +46,8 @@ function valEmail(valEmailExpression) { angular.module('umbraco.directives.validation') .directive("valEmail", valEmail) - .factory('valEmailExpression', function() { + .factory('valEmailExpression', function () { + //NOTE: This is the fixed regex which is part of the newer angular return { EMAIL_REGEXP: /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valformmanager.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valformmanager.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js index 37c0313c45..9a00d5718c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valformmanager.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js @@ -1,133 +1,133 @@ -/** -* @ngdoc directive -* @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 -* 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. -* This also sets the show-validation (or a custom) css class on the element when the form is invalid - this lets -* us css target elements to be displayed when the form is submitting/submitted. -* Another thing this directive does is to ensure that any .control-group that contains form elements that are invalid will -* be marked with the 'error' css class. This ensures that labels included in that control group are styled correctly. -**/ -function valFormManager(serverValidationManager, $rootScope, $log, $timeout, notificationsService, eventsService, $routeParams) { - return { - require: "form", - restrict: "A", - controller: function($scope) { - //This exposes an API for direct use with this directive - - var unsubscribe = []; - var self = this; - - //This is basically the same as a directive subscribing to an event but maybe a little - // nicer since the other directive can use this directive's API instead of a magical event - this.onValidationStatusChanged = function (cb) { - unsubscribe.push($scope.$on("valStatusChanged", function(evt, args) { - cb.apply(self, [evt, args]); - })); - }; - - //Ensure to remove the event handlers when this instance is destroyted - $scope.$on('$destroy', function () { - for (var u in unsubscribe) { - unsubscribe[u](); - } - }); - }, - link: function (scope, element, attr, formCtrl) { - - scope.$watch(function () { - return formCtrl.$error; - }, function (e) { - scope.$broadcast("valStatusChanged", { form: formCtrl }); - - //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"); - - //find all control group's that have no error and ensure the class is removed - var noInError = element.find(".control-group .ng-valid").closest(".control-group").not(inError); - noInError.removeClass("error"); - - }, true); - - var className = attr.valShowValidation ? attr.valShowValidation : "show-validation"; - var savingEventName = attr.savingEvent ? attr.savingEvent : "formSubmitting"; - var savedEvent = attr.savedEvent ? attr.savingEvent : "formSubmitted"; - - //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) { - element.addClass(className); - } - - var unsubscribe = []; - - //listen for the forms saving event - unsubscribe.push(scope.$on(savingEventName, function(ev, args) { - element.addClass(className); - - //set the flag so we can check to see if we should display the error. - isSavingNewItem = $routeParams.create; - })); - - //listen for the forms saved event - unsubscribe.push(scope.$on(savedEvent, function(ev, args) { - //remove validation class - element.removeClass(className); - - //clear form state as at this point we retrieve new data from the server - //and all validation will have cleared at this point - formCtrl.$setPristine(); - })); - - //This handles the 'unsaved changes' dialog which is triggered when a route is attempting to be changed but - // the form has pending changes - var locationEvent = $rootScope.$on('$locationChangeStart', function(event, nextLocation, currentLocation) { - if (!formCtrl.$dirty || isSavingNewItem) { - return; - } - - var path = nextLocation.split("#")[1]; - if (path) { - if (path.indexOf("%253") || path.indexOf("%252")) { - path = decodeURIComponent(path); - } - - if (!notificationsService.hasView()) { - var msg = { view: "confirmroutechange", args: { path: path, listener: locationEvent } }; - notificationsService.add(msg); - } - - //prevent the route! - event.preventDefault(); - - //raise an event - eventsService.emit("valFormManager.pendingChanges", true); - } - - }); - unsubscribe.push(locationEvent); - - //Ensure to remove the event handler when this instance is destroyted - scope.$on('$destroy', function() { - for (var u in unsubscribe) { - unsubscribe[u](); - } - }); - - $timeout(function(){ - formCtrl.$setPristine(); - }, 1000); - } - }; -} +/** +* @ngdoc directive +* @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 +* 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. +* This also sets the show-validation (or a custom) css class on the element when the form is invalid - this lets +* us css target elements to be displayed when the form is submitting/submitted. +* Another thing this directive does is to ensure that any .control-group that contains form elements that are invalid will +* be marked with the 'error' css class. This ensures that labels included in that control group are styled correctly. +**/ +function valFormManager(serverValidationManager, $rootScope, $log, $timeout, notificationsService, eventsService, $routeParams) { + return { + require: "form", + restrict: "A", + controller: function($scope) { + //This exposes an API for direct use with this directive + + var unsubscribe = []; + var self = this; + + //This is basically the same as a directive subscribing to an event but maybe a little + // nicer since the other directive can use this directive's API instead of a magical event + this.onValidationStatusChanged = function (cb) { + unsubscribe.push($scope.$on("valStatusChanged", function(evt, args) { + cb.apply(self, [evt, args]); + })); + }; + + //Ensure to remove the event handlers when this instance is destroyted + $scope.$on('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); + } + }); + }, + link: function (scope, element, attr, formCtrl) { + + scope.$watch(function () { + return formCtrl.$error; + }, function (e) { + scope.$broadcast("valStatusChanged", { form: formCtrl }); + + //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"); + + //find all control group's that have no error and ensure the class is removed + var noInError = element.find(".control-group .ng-valid").closest(".control-group").not(inError); + noInError.removeClass("error"); + + }, true); + + var className = attr.valShowValidation ? attr.valShowValidation : "show-validation"; + var savingEventName = attr.savingEvent ? attr.savingEvent : "formSubmitting"; + var savedEvent = attr.savedEvent ? attr.savingEvent : "formSubmitted"; + + //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) { + element.addClass(className); + } + + var unsubscribe = []; + + //listen for the forms saving event + unsubscribe.push(scope.$on(savingEventName, function(ev, args) { + element.addClass(className); + + //set the flag so we can check to see if we should display the error. + isSavingNewItem = $routeParams.create; + })); + + //listen for the forms saved event + unsubscribe.push(scope.$on(savedEvent, function(ev, args) { + //remove validation class + element.removeClass(className); + + //clear form state as at this point we retrieve new data from the server + //and all validation will have cleared at this point + formCtrl.$setPristine(); + })); + + //This handles the 'unsaved changes' dialog which is triggered when a route is attempting to be changed but + // the form has pending changes + var locationEvent = $rootScope.$on('$locationChangeStart', function(event, nextLocation, currentLocation) { + if (!formCtrl.$dirty || isSavingNewItem) { + return; + } + + var path = nextLocation.split("#")[1]; + if (path) { + if (path.indexOf("%253") || path.indexOf("%252")) { + path = decodeURIComponent(path); + } + + if (!notificationsService.hasView()) { + var msg = { view: "confirmroutechange", args: { path: path, listener: locationEvent } }; + notificationsService.add(msg); + } + + //prevent the route! + event.preventDefault(); + + //raise an event + eventsService.emit("valFormManager.pendingChanges", true); + } + + }); + unsubscribe.push(locationEvent); + + //Ensure to remove the event handler when this instance is destroyted + scope.$on('$destroy', function() { + for (var u in unsubscribe) { + unsubscribe[u](); + } + }); + + $timeout(function(){ + formCtrl.$setPristine(); + }, 1000); + } + }; +} angular.module('umbraco.directives.validation').directive("valFormManager", valFormManager); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertymsg.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js index eba308d830..be5da51702 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertymsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js @@ -1,196 +1,196 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:valPropertyMsg -* @restrict A -* @element textarea -* @requires formController -* @description This directive is used to control the display of the property level validation message. -* We will listen for server side validation changes -* and when an error is detected for this property we'll show the error message. -* In order for this directive to work, the valStatusChanged directive must be placed on the containing form. -**/ -function valPropertyMsg(serverValidationManager) { - - return { - scope: { - property: "=" - }, - require: "^form", //require that this directive is contained within an ngForm - replace: true, //replace the element with the template - restrict: "E", //restrict to element - template: "
{{errorMsg}}
", - - /** - Our directive requries a reference to a form controller - which gets passed in to this parameter - */ - link: function (scope, element, attrs, formCtrl) { - - var watcher = null; - - // Gets the error message to display - function getErrorMsg() { - //this can be null if no property was assigned - if (scope.property) { - //first try to get the error msg from the server collection - var err = serverValidationManager.getPropertyError(scope.property.alias, ""); - //if there's an error message use it - if (err && err.errorMsg) { - return err.errorMsg; - } - else { - return scope.property.propertyErrorMessage ? scope.property.propertyErrorMessage : "Property has errors"; - } - - } - return "Property has errors"; - } - - // We need to subscribe to any changes to our model (based on user input) - // This is required because when we have a server error we actually invalidate - // the form which means it cannot be resubmitted. - // So once a field is changed that has a server error assigned to it - // we need to re-validate it for the server side validator so the user can resubmit - // the form. Of course normal client-side validators will continue to execute. - function startWatch() { - //if there's not already a watch - if (!watcher) { - watcher = scope.$watch("property.value", function (newValue, oldValue) { - - if (!newValue || angular.equals(newValue, oldValue)) { - return; - } - - var errCount = 0; - for (var e in formCtrl.$error) { - if (angular.isArray(formCtrl.$error[e])) { - errCount++; - } - } - - //we are explicitly checking for valServer errors here, since we shouldn't auto clear - // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg - // is the only one, then we'll clear. - - if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) { - scope.errorMsg = ""; - formCtrl.$setValidity('valPropertyMsg', true); - stopWatch(); - } - }, true); - } - } - - //clear the watch when the property validator is valid again - function stopWatch() { - if (watcher) { - watcher(); - watcher = null; - } - } - - //if there's any remaining errors in the server validation service then we should show them. - var showValidation = serverValidationManager.items.length > 0; - var hasError = false; - - //create properties on our custom scope so we can use it in our template - scope.errorMsg = ""; - - var unsubscribe = []; - - //listen for form error changes - unsubscribe.push(scope.$on("valStatusChanged", function(evt, args) { - if (args.form.$invalid) { - - //first we need to check if the valPropertyMsg validity is invalid - if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) { - //since we already have an error we'll just return since this means we've already set the - // hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe - return; - } - else if (element.closest(".umb-control-group").find(".ng-invalid").length > 0) { - //check if it's one of the properties that is invalid in the current content property - hasError = true; - //update the validation message if we don't already have one assigned. - if (showValidation && scope.errorMsg === "") { - scope.errorMsg = getErrorMsg(); - } - } - else { - hasError = false; - scope.errorMsg = ""; - } - } - else { - hasError = false; - scope.errorMsg = ""; - } - }, true)); - - //listen for the forms saving event - unsubscribe.push(scope.$on("formSubmitting", function(ev, args) { - showValidation = true; - if (hasError && scope.errorMsg === "") { - scope.errorMsg = getErrorMsg(); - } - else if (!hasError) { - scope.errorMsg = ""; - stopWatch(); - } - })); - - //listen for the forms saved event - unsubscribe.push(scope.$on("formSubmitted", function(ev, args) { - showValidation = false; - scope.errorMsg = ""; - formCtrl.$setValidity('valPropertyMsg', true); - stopWatch(); - })); - - //listen for server validation changes - // NOTE: we pass in "" in order to listen for all validation changes to the content property, not for - // validation changes to fields in the property this is because some server side validators may not - // return the field name for which the error belongs too, just the property for which it belongs. - // It's important to note that we need to subscribe to server validation changes here because we always must - // indicate that a content property is invalid at the property level since developers may not actually implement - // the correct field validation in their property editors. - - if (scope.property) { //this can be null if no property was assigned - serverValidationManager.subscribe(scope.property.alias, "", function (isValid, propertyErrors, allErrors) { - hasError = !isValid; - if (hasError) { - //set the error message to the server message - scope.errorMsg = propertyErrors[0].errorMsg; - //flag that the current validator is invalid - formCtrl.$setValidity('valPropertyMsg', false); - startWatch(); - } - else { - scope.errorMsg = ""; - //flag that the current validator is valid - formCtrl.$setValidity('valPropertyMsg', true); - stopWatch(); - } - }); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain - // but they are a different callback instance than the above. - element.bind('$destroy', function () { - stopWatch(); - serverValidationManager.unsubscribe(scope.property.alias, ""); - }); - } - - //when the scope is disposed we need to unsubscribe - scope.$on('$destroy', function () { - for (var u in unsubscribe) { - unsubscribe[u](); - } - }); - } - - - }; -} +/** +* @ngdoc directive +* @name umbraco.directives.directive:valPropertyMsg +* @restrict A +* @element textarea +* @requires formController +* @description This directive is used to control the display of the property level validation message. +* We will listen for server side validation changes +* and when an error is detected for this property we'll show the error message. +* In order for this directive to work, the valStatusChanged directive must be placed on the containing form. +**/ +function valPropertyMsg(serverValidationManager) { + + return { + scope: { + property: "=" + }, + require: "^form", //require that this directive is contained within an ngForm + replace: true, //replace the element with the template + restrict: "E", //restrict to element + template: "
{{errorMsg}}
", + + /** + Our directive requries a reference to a form controller + which gets passed in to this parameter + */ + link: function (scope, element, attrs, formCtrl) { + + var watcher = null; + + // Gets the error message to display + function getErrorMsg() { + //this can be null if no property was assigned + if (scope.property) { + //first try to get the error msg from the server collection + var err = serverValidationManager.getPropertyError(scope.property.alias, ""); + //if there's an error message use it + if (err && err.errorMsg) { + return err.errorMsg; + } + else { + return scope.property.propertyErrorMessage ? scope.property.propertyErrorMessage : "Property has errors"; + } + + } + return "Property has errors"; + } + + // We need to subscribe to any changes to our model (based on user input) + // This is required because when we have a server error we actually invalidate + // the form which means it cannot be resubmitted. + // So once a field is changed that has a server error assigned to it + // we need to re-validate it for the server side validator so the user can resubmit + // the form. Of course normal client-side validators will continue to execute. + function startWatch() { + //if there's not already a watch + if (!watcher) { + watcher = scope.$watch("property.value", function (newValue, oldValue) { + + if (!newValue || angular.equals(newValue, oldValue)) { + return; + } + + var errCount = 0; + for (var e in formCtrl.$error) { + if (angular.isArray(formCtrl.$error[e])) { + errCount++; + } + } + + //we are explicitly checking for valServer errors here, since we shouldn't auto clear + // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg + // is the only one, then we'll clear. + + if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) { + scope.errorMsg = ""; + formCtrl.$setValidity('valPropertyMsg', true); + stopWatch(); + } + }, true); + } + } + + //clear the watch when the property validator is valid again + function stopWatch() { + if (watcher) { + watcher(); + watcher = null; + } + } + + //if there's any remaining errors in the server validation service then we should show them. + var showValidation = serverValidationManager.items.length > 0; + var hasError = false; + + //create properties on our custom scope so we can use it in our template + scope.errorMsg = ""; + + var unsubscribe = []; + + //listen for form error changes + unsubscribe.push(scope.$on("valStatusChanged", function(evt, args) { + if (args.form.$invalid) { + + //first we need to check if the valPropertyMsg validity is invalid + if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) { + //since we already have an error we'll just return since this means we've already set the + // hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe + return; + } + else if (element.closest(".umb-control-group").find(".ng-invalid").length > 0) { + //check if it's one of the properties that is invalid in the current content property + hasError = true; + //update the validation message if we don't already have one assigned. + if (showValidation && scope.errorMsg === "") { + scope.errorMsg = getErrorMsg(); + } + } + else { + hasError = false; + scope.errorMsg = ""; + } + } + else { + hasError = false; + scope.errorMsg = ""; + } + }, true)); + + //listen for the forms saving event + unsubscribe.push(scope.$on("formSubmitting", function(ev, args) { + showValidation = true; + if (hasError && scope.errorMsg === "") { + scope.errorMsg = getErrorMsg(); + } + else if (!hasError) { + scope.errorMsg = ""; + stopWatch(); + } + })); + + //listen for the forms saved event + unsubscribe.push(scope.$on("formSubmitted", function(ev, args) { + showValidation = false; + scope.errorMsg = ""; + formCtrl.$setValidity('valPropertyMsg', true); + stopWatch(); + })); + + //listen for server validation changes + // NOTE: we pass in "" in order to listen for all validation changes to the content property, not for + // validation changes to fields in the property this is because some server side validators may not + // return the field name for which the error belongs too, just the property for which it belongs. + // It's important to note that we need to subscribe to server validation changes here because we always must + // indicate that a content property is invalid at the property level since developers may not actually implement + // the correct field validation in their property editors. + + if (scope.property) { //this can be null if no property was assigned + serverValidationManager.subscribe(scope.property.alias, "", function (isValid, propertyErrors, allErrors) { + hasError = !isValid; + if (hasError) { + //set the error message to the server message + scope.errorMsg = propertyErrors[0].errorMsg; + //flag that the current validator is invalid + formCtrl.$setValidity('valPropertyMsg', false); + startWatch(); + } + else { + scope.errorMsg = ""; + //flag that the current validator is valid + formCtrl.$setValidity('valPropertyMsg', true); + stopWatch(); + } + }); + + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain + // but they are a different callback instance than the above. + element.bind('$destroy', function () { + stopWatch(); + serverValidationManager.unsubscribe(scope.property.alias, ""); + }); + } + + //when the scope is disposed we need to unsubscribe + scope.$on('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); + } + }); + } + + + }; +} angular.module('umbraco.directives.validation').directive("valPropertyMsg", valPropertyMsg); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertyvalidator.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valpropertyvalidator.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valregex.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valregex.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js index 7bc3c6b877..6406583e77 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valregex.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js @@ -1,78 +1,78 @@ -/** - * @ngdoc directive - * @name umbraco.directives.directive:valRegex - * @restrict A - * @description A custom directive to allow for matching a value against a regex string. - * NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string - **/ -function valRegex() { - - return { - require: 'ngModel', - restrict: "A", - link: function (scope, elm, attrs, ctrl) { - - var flags = ""; - var regex; - var eventBindings = []; - - attrs.$observe("valRegexFlags", function (newVal) { - if (newVal) { - flags = newVal; - } - }); - - attrs.$observe("valRegex", function (newVal) { - if (newVal) { - try { - var resolved = newVal; - if (resolved) { - regex = new RegExp(resolved, flags); - } - else { - regex = new RegExp(attrs.valRegex, flags); - } - } - catch (e) { - regex = new RegExp(attrs.valRegex, flags); - } - } - }); - - eventBindings.push(scope.$watch('ngModel', function(newValue, oldValue){ - if(newValue && newValue !== oldValue) { - patternValidator(newValue); - } - })); - - var patternValidator = function (viewValue) { - if (regex) { - //NOTE: we don't validate on empty values, use required validator for that - if (!viewValue || regex.test(viewValue.toString())) { - // it is valid - ctrl.$setValidity('valRegex', true); - //assign a message to the validator - ctrl.errorMsg = ""; - return viewValue; - } - else { - // it is invalid, return undefined (no model update) - ctrl.$setValidity('valRegex', false); - //assign a message to the validator - ctrl.errorMsg = "Value is invalid, it does not match the correct pattern"; - return undefined; - } - } - }; - - scope.$on('$destroy', function(){ - // unbind watchers - for(var e in eventBindings) { - eventBindings[e](); - } - }); - - } - }; -} -angular.module('umbraco.directives.validation').directive("valRegex", valRegex); +/** + * @ngdoc directive + * @name umbraco.directives.directive:valRegex + * @restrict A + * @description A custom directive to allow for matching a value against a regex string. + * NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string + **/ +function valRegex() { + + return { + require: 'ngModel', + restrict: "A", + link: function (scope, elm, attrs, ctrl) { + + var flags = ""; + var regex; + var eventBindings = []; + + attrs.$observe("valRegexFlags", function (newVal) { + if (newVal) { + flags = newVal; + } + }); + + attrs.$observe("valRegex", function (newVal) { + if (newVal) { + try { + var resolved = newVal; + if (resolved) { + regex = new RegExp(resolved, flags); + } + else { + regex = new RegExp(attrs.valRegex, flags); + } + } + catch (e) { + regex = new RegExp(attrs.valRegex, flags); + } + } + }); + + eventBindings.push(scope.$watch('ngModel', function(newValue, oldValue){ + if(newValue && newValue !== oldValue) { + patternValidator(newValue); + } + })); + + var patternValidator = function (viewValue) { + if (regex) { + //NOTE: we don't validate on empty values, use required validator for that + if (!viewValue || regex.test(viewValue.toString())) { + // it is valid + ctrl.$setValidity('valRegex', true); + //assign a message to the validator + ctrl.errorMsg = ""; + return viewValue; + } + else { + // it is invalid, return undefined (no model update) + ctrl.$setValidity('valRegex', false); + //assign a message to the validator + ctrl.errorMsg = "Value is invalid, it does not match the correct pattern"; + return undefined; + } + } + }; + + scope.$on('$destroy', function(){ + // unbind watchers + for(var e in eventBindings) { + eventBindings[e](); + } + }); + + } + }; +} +angular.module('umbraco.directives.validation').directive("valRegex", valRegex); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserver.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserver.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js index 6225485073..1432a713c0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserver.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js @@ -1,94 +1,94 @@ -/** - * @ngdoc directive - * @name umbraco.directives.directive:valServer - * @restrict A - * @description This directive is used to associate a content property with a server-side validation response - * so that the validators in angular are updated based on server-side feedback. - **/ -function valServer(serverValidationManager) { - return { - require: ['ngModel', '?^umbProperty'], - restrict: "A", - link: function (scope, element, attr, ctrls) { - - var modelCtrl = ctrls[0]; - var umbPropCtrl = ctrls.length > 1 ? ctrls[1] : null; - if (!umbPropCtrl) { - //we cannot proceed, this validator will be disabled - return; - } - - var watcher = null; - - //Need to watch the value model for it to change, previously we had subscribed to - //modelCtrl.$viewChangeListeners but this is not good enough if you have an editor that - // doesn't specifically have a 2 way ng binding. This is required because when we - // have a server error we actually invalidate the form which means it cannot be - // resubmitted. So once a field is changed that has a server error assigned to it - // we need to re-validate it for the server side validator so the user can resubmit - // the form. Of course normal client-side validators will continue to execute. - function startWatch() { - //if there's not already a watch - if (!watcher) { - watcher = scope.$watch(function () { - return modelCtrl.$modelValue; - }, function (newValue, oldValue) { - - if (!newValue || angular.equals(newValue, oldValue)) { - return; - } - - if (modelCtrl.$invalid) { - modelCtrl.$setValidity('valServer', true); - stopWatch(); - } - }, true); - } - } - - function stopWatch() { - if (watcher) { - watcher(); - watcher = null; - } - } - - var currentProperty = umbPropCtrl.property; - - //default to 'value' if nothing is set - var fieldName = "value"; - if (attr.valServer) { - fieldName = scope.$eval(attr.valServer); - if (!fieldName) { - //eval returned nothing so just use the string - fieldName = attr.valServer; - } - } - - //subscribe to the server validation changes - serverValidationManager.subscribe(currentProperty.alias, fieldName, function (isValid, propertyErrors, allErrors) { - if (!isValid) { - modelCtrl.$setValidity('valServer', false); - //assign an error msg property to the current validator - modelCtrl.errorMsg = propertyErrors[0].errorMsg; - startWatch(); - } - else { - modelCtrl.$setValidity('valServer', true); - //reset the error message - modelCtrl.errorMsg = ""; - stopWatch(); - } - }); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain - // but they are a different callback instance than the above. - element.bind('$destroy', function () { - stopWatch(); - serverValidationManager.unsubscribe(currentProperty.alias, fieldName); - }); - } - }; -} +/** + * @ngdoc directive + * @name umbraco.directives.directive:valServer + * @restrict A + * @description This directive is used to associate a content property with a server-side validation response + * so that the validators in angular are updated based on server-side feedback. + **/ +function valServer(serverValidationManager) { + return { + require: ['ngModel', '?^umbProperty'], + restrict: "A", + link: function (scope, element, attr, ctrls) { + + var modelCtrl = ctrls[0]; + var umbPropCtrl = ctrls.length > 1 ? ctrls[1] : null; + if (!umbPropCtrl) { + //we cannot proceed, this validator will be disabled + return; + } + + var watcher = null; + + //Need to watch the value model for it to change, previously we had subscribed to + //modelCtrl.$viewChangeListeners but this is not good enough if you have an editor that + // doesn't specifically have a 2 way ng binding. This is required because when we + // have a server error we actually invalidate the form which means it cannot be + // resubmitted. So once a field is changed that has a server error assigned to it + // we need to re-validate it for the server side validator so the user can resubmit + // the form. Of course normal client-side validators will continue to execute. + function startWatch() { + //if there's not already a watch + if (!watcher) { + watcher = scope.$watch(function () { + return modelCtrl.$modelValue; + }, function (newValue, oldValue) { + + if (!newValue || angular.equals(newValue, oldValue)) { + return; + } + + if (modelCtrl.$invalid) { + modelCtrl.$setValidity('valServer', true); + stopWatch(); + } + }, true); + } + } + + function stopWatch() { + if (watcher) { + watcher(); + watcher = null; + } + } + + var currentProperty = umbPropCtrl.property; + + //default to 'value' if nothing is set + var fieldName = "value"; + if (attr.valServer) { + fieldName = scope.$eval(attr.valServer); + if (!fieldName) { + //eval returned nothing so just use the string + fieldName = attr.valServer; + } + } + + //subscribe to the server validation changes + serverValidationManager.subscribe(currentProperty.alias, fieldName, function (isValid, propertyErrors, allErrors) { + if (!isValid) { + modelCtrl.$setValidity('valServer', false); + //assign an error msg property to the current validator + modelCtrl.errorMsg = propertyErrors[0].errorMsg; + startWatch(); + } + else { + modelCtrl.$setValidity('valServer', true); + //reset the error message + modelCtrl.errorMsg = ""; + stopWatch(); + } + }); + + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain + // but they are a different callback instance than the above. + element.bind('$destroy', function () { + stopWatch(); + serverValidationManager.unsubscribe(currentProperty.alias, fieldName); + }); + } + }; +} angular.module('umbraco.directives.validation').directive("valServer", valServer); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserverfield.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserverfield.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js index 46fbaf93dd..1e0d2d8ba5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valserverfield.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js @@ -1,65 +1,65 @@ -/** - * @ngdoc directive - * @name umbraco.directives.directive:valServerField - * @restrict A - * @description This directive is used to associate a content field (not user defined) with a server-side validation response - * so that the validators in angular are updated based on server-side feedback. - **/ -function valServerField(serverValidationManager) { - return { - require: 'ngModel', - restrict: "A", - link: function (scope, element, attr, ctrl) { - - var fieldName = null; - var eventBindings = []; - - attr.$observe("valServerField", function (newVal) { - if (newVal && fieldName === null) { - fieldName = newVal; - - //subscribe to the changed event of the view model. This is required because when we - // have a server error we actually invalidate the form which means it cannot be - // resubmitted. So once a field is changed that has a server error assigned to it - // we need to re-validate it for the server side validator so the user can resubmit - // the form. Of course normal client-side validators will continue to execute. - eventBindings.push(scope.$watch('ngModel', function(newValue){ - if (ctrl.$invalid) { - ctrl.$setValidity('valServerField', true); - } - })); - - //subscribe to the server validation changes - serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) { - if (!isValid) { - ctrl.$setValidity('valServerField', false); - //assign an error msg property to the current validator - ctrl.errorMsg = fieldErrors[0].errorMsg; - } - else { - ctrl.$setValidity('valServerField', true); - //reset the error message - ctrl.errorMsg = ""; - } - }); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain - // but they are a different callback instance than the above. - element.bind('$destroy', function () { - serverValidationManager.unsubscribe(null, fieldName); - }); - } - }); - - scope.$on('$destroy', function(){ - // unbind watchers - for(var e in eventBindings) { - eventBindings[e](); - } - }); - - } - }; -} -angular.module('umbraco.directives.validation').directive("valServerField", valServerField); +/** + * @ngdoc directive + * @name umbraco.directives.directive:valServerField + * @restrict A + * @description This directive is used to associate a content field (not user defined) with a server-side validation response + * so that the validators in angular are updated based on server-side feedback. + **/ +function valServerField(serverValidationManager) { + return { + require: 'ngModel', + restrict: "A", + link: function (scope, element, attr, ctrl) { + + var fieldName = null; + var eventBindings = []; + + attr.$observe("valServerField", function (newVal) { + if (newVal && fieldName === null) { + fieldName = newVal; + + //subscribe to the changed event of the view model. This is required because when we + // have a server error we actually invalidate the form which means it cannot be + // resubmitted. So once a field is changed that has a server error assigned to it + // we need to re-validate it for the server side validator so the user can resubmit + // the form. Of course normal client-side validators will continue to execute. + eventBindings.push(scope.$watch('ngModel', function(newValue){ + if (ctrl.$invalid) { + ctrl.$setValidity('valServerField', true); + } + })); + + //subscribe to the server validation changes + serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) { + if (!isValid) { + ctrl.$setValidity('valServerField', false); + //assign an error msg property to the current validator + ctrl.errorMsg = fieldErrors[0].errorMsg; + } + else { + ctrl.$setValidity('valServerField', true); + //reset the error message + ctrl.errorMsg = ""; + } + }); + + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain + // but they are a different callback instance than the above. + element.bind('$destroy', function () { + serverValidationManager.unsubscribe(null, fieldName); + }); + } + }); + + scope.$on('$destroy', function(){ + // unbind watchers + for(var e in eventBindings) { + eventBindings[e](); + } + }); + + } + }; +} +angular.module('umbraco.directives.validation').directive("valServerField", valServerField); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtab.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtab.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js index fbca0cd233..8d1fc60083 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtab.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js @@ -1,38 +1,38 @@ - -/** -* @ngdoc directive -* @name umbraco.directives.directive:valTab -* @restrict A -* @description Used to show validation warnings for a tab to indicate that the tab content has validations errors in its data. -* In order for this directive to work, the valFormManager directive must be placed on the containing form. -**/ -function valTab() { - return { - require: ['^form', '^valFormManager'], - restrict: "A", - link: function (scope, element, attr, ctrs) { - - var valFormManager = ctrs[1]; - var tabId = "tab" + scope.tab.id; - scope.tabHasError = false; - - //listen for form validation changes - valFormManager.onValidationStatusChanged(function (evt, args) { - if (!args.form.$valid) { - var tabContent = element.closest(".umb-panel").find("#" + tabId); - //check if the validation messages are contained inside of this tabs - if (tabContent.find(".ng-invalid").length > 0) { - scope.tabHasError = true; - } else { - scope.tabHasError = false; - } - } - else { - scope.tabHasError = false; - } - }); - - } - }; -} + +/** +* @ngdoc directive +* @name umbraco.directives.directive:valTab +* @restrict A +* @description Used to show validation warnings for a tab to indicate that the tab content has validations errors in its data. +* In order for this directive to work, the valFormManager directive must be placed on the containing form. +**/ +function valTab() { + return { + require: ['^form', '^valFormManager'], + restrict: "A", + link: function (scope, element, attr, ctrs) { + + var valFormManager = ctrs[1]; + var tabId = "tab" + scope.tab.id; + scope.tabHasError = false; + + //listen for form validation changes + valFormManager.onValidationStatusChanged(function (evt, args) { + if (!args.form.$valid) { + var tabContent = element.closest(".umb-panel").find("#" + tabId); + //check if the validation messages are contained inside of this tabs + if (tabContent.find(".ng-invalid").length > 0) { + scope.tabHasError = true; + } else { + scope.tabHasError = false; + } + } + else { + scope.tabHasError = false; + } + }); + + } + }; +} angular.module('umbraco.directives.validation').directive("valTab", valTab); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtogglemsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtogglemsg.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js index 43792a708a..304f151274 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtogglemsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js @@ -1,90 +1,90 @@ -function valToggleMsg(serverValidationManager) { - return { - require: "^form", - restrict: "A", - - /** - Our directive requries a reference to a form controller which gets passed in to this parameter - */ - link: function (scope, element, attr, formCtrl) { - - if (!attr.valToggleMsg){ - throw "valToggleMsg requires that a reference to a validator is specified"; - } - if (!attr.valMsgFor){ - throw "valToggleMsg requires that the attribute valMsgFor exists on the element"; - } - if (!formCtrl[attr.valMsgFor]) { - throw "valToggleMsg cannot find field " + attr.valMsgFor + " on form " + formCtrl.$name; - } - - //if there's any remaining errors in the server validation service then we should show them. - var showValidation = serverValidationManager.items.length > 0; - var hasCustomMsg = element.contents().length > 0; - - //add a watch to the validator for the value (i.e. myForm.value.$error.required ) - scope.$watch(function () { - //sometimes if a dialog closes in the middle of digest we can get null references here - - return (formCtrl && formCtrl[attr.valMsgFor]) ? formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] : null; - }, function () { - //sometimes if a dialog closes in the middle of digest we can get null references here - if ((formCtrl && formCtrl[attr.valMsgFor])) { - if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] && showValidation) { - element.show(); - //display the error message if this element has no contents - if (!hasCustomMsg) { - element.html(formCtrl[attr.valMsgFor].errorMsg); - } - } - else { - element.hide(); - } - } - }); - - var unsubscribe = []; - - //listen for the saving event (the result is a callback method which is called to unsubscribe) - unsubscribe.push(scope.$on("formSubmitting", function(ev, args) { - showValidation = true; - if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg]) { - element.show(); - //display the error message if this element has no contents - if (!hasCustomMsg) { - element.html(formCtrl[attr.valMsgFor].errorMsg); - } - } - else { - element.hide(); - } - })); - - //listen for the saved event (the result is a callback method which is called to unsubscribe) - unsubscribe.push(scope.$on("formSubmitted", function(ev, args) { - showValidation = false; - element.hide(); - })); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise if this directive is part of a modal, the listener still exists because the dom - // element might still be there even after the modal has been hidden. - element.bind('$destroy', function () { - for (var u in unsubscribe) { - unsubscribe[u](); - } - }); - - } - }; -} - -/** -* @ngdoc directive -* @name umbraco.directives.directive:valToggleMsg -* @restrict A -* @element input -* @requires formController -* @description This directive will show/hide an error based on: is the value + the given validator invalid? AND, has the form been submitted ? -**/ +function valToggleMsg(serverValidationManager) { + return { + require: "^form", + restrict: "A", + + /** + Our directive requries a reference to a form controller which gets passed in to this parameter + */ + link: function (scope, element, attr, formCtrl) { + + if (!attr.valToggleMsg){ + throw "valToggleMsg requires that a reference to a validator is specified"; + } + if (!attr.valMsgFor){ + throw "valToggleMsg requires that the attribute valMsgFor exists on the element"; + } + if (!formCtrl[attr.valMsgFor]) { + throw "valToggleMsg cannot find field " + attr.valMsgFor + " on form " + formCtrl.$name; + } + + //if there's any remaining errors in the server validation service then we should show them. + var showValidation = serverValidationManager.items.length > 0; + var hasCustomMsg = element.contents().length > 0; + + //add a watch to the validator for the value (i.e. myForm.value.$error.required ) + scope.$watch(function () { + //sometimes if a dialog closes in the middle of digest we can get null references here + + return (formCtrl && formCtrl[attr.valMsgFor]) ? formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] : null; + }, function () { + //sometimes if a dialog closes in the middle of digest we can get null references here + if ((formCtrl && formCtrl[attr.valMsgFor])) { + if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] && showValidation) { + element.show(); + //display the error message if this element has no contents + if (!hasCustomMsg) { + element.html(formCtrl[attr.valMsgFor].errorMsg); + } + } + else { + element.hide(); + } + } + }); + + var unsubscribe = []; + + //listen for the saving event (the result is a callback method which is called to unsubscribe) + unsubscribe.push(scope.$on("formSubmitting", function(ev, args) { + showValidation = true; + if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg]) { + element.show(); + //display the error message if this element has no contents + if (!hasCustomMsg) { + element.html(formCtrl[attr.valMsgFor].errorMsg); + } + } + else { + element.hide(); + } + })); + + //listen for the saved event (the result is a callback method which is called to unsubscribe) + unsubscribe.push(scope.$on("formSubmitted", function(ev, args) { + showValidation = false; + element.hide(); + })); + + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise if this directive is part of a modal, the listener still exists because the dom + // element might still be there even after the modal has been hidden. + element.bind('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); + } + }); + + } + }; +} + +/** +* @ngdoc directive +* @name umbraco.directives.directive:valToggleMsg +* @restrict A +* @element input +* @requires formController +* @description This directive will show/hide an error based on: is the value + the given validator invalid? AND, has the form been submitted ? +**/ angular.module('umbraco.directives.validation').directive("valToggleMsg", valToggleMsg); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtriggerchange.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtriggerchange.directive.js similarity index 100% rename from src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valtriggerchange.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valtriggerchange.directive.js 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 a9296acc37..914b601249 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 @@ -109,16 +109,6 @@ function entityResource($q, $http, umbRequestHelper) { [{ id: id}, {type: type }])), 'Failed to retrieve entity data for id ' + id); }, - - getByQuery: function (query, nodeContextId, type) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetByQuery", - [{query: query},{ nodeContextId: nodeContextId}, {type: type }])), - 'Failed to retrieve entity data for query ' + query); - }, /** * @ngdoc method @@ -168,7 +158,41 @@ function entityResource($q, $http, umbRequestHelper) { /** * @ngdoc method - * @name umbraco.resources.entityResource#getEntityById + * @name umbraco.resources.entityResource#getByQuery + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity from a given xpath + * + * ##usage + *
+         * //get content by xpath
+         * entityResource.getByQuery("$current", -1, "Document")
+         *    .then(function(ent) {
+         *        var myDoc = ent; 
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {string} query xpath to use in query + * @param {Int} nodeContextId id id to start from + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getByQuery: function (query, nodeContextId, type) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetByQuery", + [{ query: query }, { nodeContextId: nodeContextId }, { type: type }])), + 'Failed to retrieve entity data for query ' + query); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getAll * @methodOf umbraco.resources.entityResource * * @description @@ -260,7 +284,7 @@ function entityResource($q, $http, umbRequestHelper) { /** * @ngdoc method - * @name umbraco.resources.entityResource#searchMedia + * @name umbraco.resources.entityResource#search * @methodOf umbraco.resources.entityResource * * @description diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/healthcheck.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/healthcheck.resource.js new file mode 100644 index 0000000000..17d2651a52 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/healthcheck.resource.js @@ -0,0 +1,76 @@ +/** + * @ngdoc service + * @name umbraco.resources.healthCheckResource + * @function + * + * @description + * Used by the health check dashboard to get checks and send requests to fix checks. + */ +(function () { + 'use strict'; + + function healthCheckResource($http, umbRequestHelper) { + + /** + * @ngdoc function + * @name umbraco.resources.healthCheckService#getAllChecks + * @methodOf umbraco.resources.healthCheckResource + * @function + * + * @description + * Called to get all available health checks + */ + function getAllChecks() { + return umbRequestHelper.resourcePromise( + $http.get(Umbraco.Sys.ServerVariables.umbracoUrls.healthCheckBaseUrl + "GetAllHealthChecks"), + "Failed to retrieve health checks" + ); + } + + /** + * @ngdoc function + * @name umbraco.resources.healthCheckService#getStatus + * @methodOf umbraco.resources.healthCheckResource + * @function + * + * @description + * Called to get execute a health check and return the check status + */ + function getStatus(id) { + return umbRequestHelper.resourcePromise( + $http.get(Umbraco.Sys.ServerVariables.umbracoUrls.healthCheckBaseUrl + 'GetStatus?id=' + id), + 'Failed to retrieve status for health check with ID ' + id + ); + } + + /** + * @ngdoc function + * @name umbraco.resources.healthCheckService#executeAction + * @methodOf umbraco.resources.healthCheckResource + * @function + * + * @description + * Called to execute a health check action (rectifying an issue) + */ + function executeAction(action) { + return umbRequestHelper.resourcePromise( + $http.post(Umbraco.Sys.ServerVariables.umbracoUrls.healthCheckBaseUrl + 'ExecuteAction', action), + 'Failed to execute action with alias ' + action.alias + ' and healthCheckId + ' + action.healthCheckId + ); + } + + var resource = { + getAllChecks: getAllChecks, + getStatus: getStatus, + executeAction: executeAction + }; + + return resource; + + } + + + angular.module('umbraco.resources').factory('healthCheckResource', healthCheckResource); + + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/ourpackagerrepository.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/ourpackagerrepository.resource.js new file mode 100644 index 0000000000..053aaf1394 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/ourpackagerrepository.resource.js @@ -0,0 +1,68 @@ +/** + * @ngdoc service + * @name umbraco.resources.ourPackageRepositoryResource + * @description handles data for package installations + **/ +function ourPackageRepositoryResource($q, $http, umbDataFormatter, umbRequestHelper) { + + //var baseurl = "http://localhost:24292/webapi/packages/v1"; + var baseurl = "https://our.umbraco.org/webapi/packages/v1"; + + return { + + getDetails: function (packageId) { + + return umbRequestHelper.resourcePromise( + $http.get(baseurl + "/" + packageId), + 'Failed to get package details'); + }, + + getCategories: function () { + + return umbRequestHelper.resourcePromise( + $http.get(baseurl), + 'Failed to query packages'); + }, + + getPopular: function (maxResults, category) { + + if (maxResults === undefined) { + maxResults = 10; + } + if (category === undefined) { + category = ""; + } + + return umbRequestHelper.resourcePromise( + $http.get(baseurl + "?pageIndex=0&pageSize=" + maxResults + "&category=" + category + "&order=Popular&version=" + Umbraco.Sys.ServerVariables.application.version), + 'Failed to query packages'); + }, + + search: function (pageIndex, pageSize, orderBy, category, query, canceler) { + + var httpConfig = {}; + if (canceler) { + httpConfig["timeout"] = canceler; + } + + if (category === undefined) { + category = ""; + } + if (query === undefined) { + query = ""; + } + + //order by score if there is nothing set + var order = !orderBy ? "&order=Default" : ("&order=" + orderBy); + + return umbRequestHelper.resourcePromise( + $http.get(baseurl + "?pageIndex=" + pageIndex + "&pageSize=" + pageSize + "&category=" + category + "&query=" + query + order + "&version=" + Umbraco.Sys.ServerVariables.application.version), + httpConfig, + 'Failed to query packages'); + } + + + }; +} + +angular.module('umbraco.resources').factory('ourPackageRepositoryResource', ourPackageRepositoryResource); 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 c1d95861eb..9dae2008e2 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 @@ -7,6 +7,49 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) { return { + /** + * @ngdoc method + * @name umbraco.resources.packageInstallResource#getInstalled + * @methodOf umbraco.resources.packageInstallResource + * + * @description + * Gets a list of installed packages + */ + getInstalled: function() { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "packageInstallApiBaseUrl", + "GetInstalled")), + 'Failed to get installed packages'); + }, + + validateInstalled: function (name, version) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "packageInstallApiBaseUrl", + "ValidateInstalled", { name: name, version: version })), + 'Failed to validate package ' + name); + }, + + deleteCreatedPackage: function (packageId) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "packageInstallApiBaseUrl", + "DeleteCreatedPackage", { packageId: packageId })), + 'Failed to delete package ' + packageId); + }, + + uninstall: function(packageId) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "packageInstallApiBaseUrl", + "Uninstall", { packageId: packageId })), + 'Failed to uninstall package'); + }, /** * @ngdoc method 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 new file mode 100644 index 0000000000..80f6bc023e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/redirecturls.resource.js @@ -0,0 +1,119 @@ +/** + * @ngdoc service + * @name umbraco.resources.redirectUrlResource + * @function + * + * @description + * Used by the redirect url dashboard to get urls and send requests to remove redirects. + */ +(function() { + 'use strict'; + + function redirectUrlsResource($http, umbRequestHelper) { + + /** + * @ngdoc function + * @name umbraco.resources.redirectUrlResource#searchRedirectUrls + * @methodOf umbraco.resources.redirectUrlResource + * @function + * + * @description + * Called to search redirects + * ##usage + *
+         * redirectUrlsResource.searchRedirectUrls("", 0, 20)
+         *    .then(function(response) {
+         *
+         *    });
+         * 
+ * @param {String} searchTerm Searh term + * @param {Int} pageIndex index of the page to retrive items from + * @param {Int} pageSize The number of items on a page + */ + function searchRedirectUrls(searchTerm, pageIndex, pageSize) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "redirectUrlManagementApiBaseUrl", + "SearchRedirectUrls", + { searchTerm: searchTerm, page: pageIndex, pageSize: pageSize })), + 'Failed to retrieve data for searching redirect urls'); + } + + function isEnabled() { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "redirectUrlManagementApiBaseUrl", + "IsEnabled")), + 'Failed to retrieve data to check if the 301 redirect is enabled'); + } + + /** + * @ngdoc function + * @name umbraco.resources.redirectUrlResource#deleteRedirectUrl + * @methodOf umbraco.resources.redirectUrlResource + * @function + * + * @description + * Called to delete a redirect + * ##usage + *
+         * redirectUrlsResource.deleteRedirectUrl(1234)
+         *    .then(function() {
+         *
+         *    });
+         * 
+ * @param {Int} id Id of the redirect + */ + function deleteRedirectUrl(id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "redirectUrlManagementApiBaseUrl", + "DeleteRedirectUrl", { id: id })), + 'Failed to remove redirect'); + } + + /** + * @ngdoc function + * @name umbraco.resources.redirectUrlResource#toggleUrlTracker + * @methodOf umbraco.resources.redirectUrlResource + * @function + * + * @description + * Called to enable or disable redirect url tracker + * ##usage + *
+         * redirectUrlsResource.toggleUrlTracker(true)
+         *    .then(function() {
+         *
+         *    });
+         * 
+ * @param {Bool} disable true/false to disable/enable the url tracker + */ + function toggleUrlTracker(disable) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "redirectUrlManagementApiBaseUrl", + "ToggleUrlTracker", { disable: disable })), + 'Failed to toggle redirect url tracker'); + } + + var resource = { + searchRedirectUrls: searchRedirectUrls, + deleteRedirectUrl: deleteRedirectUrl, + toggleUrlTracker: toggleUrlTracker, + isEnabled: isEnabled + }; + + return resource; + + } + + angular.module('umbraco.resources').factory('redirectUrlsResource', redirectUrlsResource); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/security/_module.js b/src/Umbraco.Web.UI.Client/src/common/security/_module.js index 15a7663d9b..c8289c754e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/security/_module.js +++ b/src/Umbraco.Web.UI.Client/src/common/security/_module.js @@ -1,4 +1,4 @@ -// Based loosely around work by Witold Szczerba - https://github.com/witoldsz/angular-http-auth -angular.module('umbraco.security', [ - 'umbraco.security.retryQueue', - 'umbraco.security.interceptor']); \ No newline at end of file +//TODO: This is silly and unecessary to have a separate module for this +angular.module('umbraco.security.retryQueue', []); +angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue']); +angular.module('umbraco.security', ['umbraco.security.retryQueue', 'umbraco.security.interceptor']); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js b/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js index e971719398..28d91dd610 100644 --- a/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js +++ b/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js @@ -1,3 +1,4 @@ +//TODO: This is silly and unecessary to have a separate module for this angular.module('umbraco.security.retryQueue', []) // This is a generic retry queue for security failures. Each item is expected to expose two functions: retry and cancel. diff --git a/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js b/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js similarity index 96% rename from src/Umbraco.Web.UI.Client/src/common/security/interceptor.js rename to src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js index 2b757707ba..b80754ef67 100644 --- a/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js +++ b/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js @@ -1,95 +1,95 @@ -angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue']) - // This http interceptor listens for authentication successes and failures - .factory('securityInterceptor', ['$injector', 'securityRetryQueue', 'notificationsService', 'requestInterceptorFilter', function ($injector, queue, notifications, requestInterceptorFilter) { - return function(promise) { - - return promise.then( - function(originalResponse) { - // Intercept successful requests - - //Here we'll check if our custom header is in the response which indicates how many seconds the user's session has before it - //expires. Then we'll update the user in the user service accordingly. - var headers = originalResponse.headers(); - if (headers["x-umb-user-seconds"]) { - // We must use $injector to get the $http service to prevent circular dependency - var userService = $injector.get('userService'); - userService.setUserTimeout(headers["x-umb-user-seconds"]); - } - - return promise; - }, function(originalResponse) { - // Intercept failed requests - - //Here we'll check if we should ignore the error, this will be based on an original header set - var headers = originalResponse.config ? originalResponse.config.headers : {}; - if (headers["x-umb-ignore-error"] === "ignore") { - //exit/ignore - return promise; - } - var filtered = _.find(requestInterceptorFilter(), function(val) { - return originalResponse.config.url.indexOf(val) > 0; - }); - if (filtered) { - return promise; - } - - //A 401 means that the user is not logged in - if (originalResponse.status === 401) { - - // The request bounced because it was not authorized - add a new request to the retry queue - promise = queue.pushRetryFn('unauthorized-server', function retryRequest() { - // We must use $injector to get the $http service to prevent circular dependency - return $injector.get('$http')(originalResponse.config); - }); - } - else if (originalResponse.status === 404) { - - //a 404 indicates that the request was not found - this could be due to a non existing url, or it could - //be due to accessing a url with a parameter that doesn't exist, either way we should notifiy the user about it - - var errMsg = "The URL returned a 404 (not found):
" + originalResponse.config.url.split('?')[0] + ""; - if (originalResponse.data && originalResponse.data.ExceptionMessage) { - errMsg += "
with error:
" + originalResponse.data.ExceptionMessage + ""; - } - if (originalResponse.config.data) { - errMsg += "
with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information."; - } - - notifications.error( - "Request error", - errMsg); - - } - else if (originalResponse.status === 403) { - //if the status was a 403 it means the user didn't have permission to do what the request was trying to do. - //How do we deal with this now, need to tell the user somehow that they don't have permission to do the thing that was - //requested. We can either deal with this globally here, or we can deal with it globally for individual requests on the umbRequestHelper, - // or completely custom for services calling resources. - - //http://issues.umbraco.org/issue/U4-2749 - - //It was decided to just put these messages into the normal status messages. - - var msg = "Unauthorized access to URL:
" + originalResponse.config.url.split('?')[0] + ""; - if (originalResponse.config.data) { - msg += "
with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information."; - } - - notifications.error( - "Authorization error", - msg); - } - - return promise; - }); - }; - }]) - - .value('requestInterceptorFilter', function() { - return ["www.gravatar.com"]; - }) - - // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block. - .config(['$httpProvider', function ($httpProvider) { - $httpProvider.responseInterceptors.push('securityInterceptor'); +angular.module('umbraco.security.interceptor') + // This http interceptor listens for authentication successes and failures + .factory('securityInterceptor', ['$injector', 'securityRetryQueue', 'notificationsService', 'requestInterceptorFilter', function ($injector, queue, notifications, requestInterceptorFilter) { + return function(promise) { + + return promise.then( + function(originalResponse) { + // Intercept successful requests + + //Here we'll check if our custom header is in the response which indicates how many seconds the user's session has before it + //expires. Then we'll update the user in the user service accordingly. + var headers = originalResponse.headers(); + if (headers["x-umb-user-seconds"]) { + // We must use $injector to get the $http service to prevent circular dependency + var userService = $injector.get('userService'); + userService.setUserTimeout(headers["x-umb-user-seconds"]); + } + + return promise; + }, function(originalResponse) { + // Intercept failed requests + + //Here we'll check if we should ignore the error, this will be based on an original header set + var headers = originalResponse.config ? originalResponse.config.headers : {}; + if (headers["x-umb-ignore-error"] === "ignore") { + //exit/ignore + return promise; + } + var filtered = _.find(requestInterceptorFilter(), function(val) { + return originalResponse.config.url.indexOf(val) > 0; + }); + if (filtered) { + return promise; + } + + //A 401 means that the user is not logged in + if (originalResponse.status === 401) { + + // The request bounced because it was not authorized - add a new request to the retry queue + promise = queue.pushRetryFn('unauthorized-server', function retryRequest() { + // We must use $injector to get the $http service to prevent circular dependency + return $injector.get('$http')(originalResponse.config); + }); + } + else if (originalResponse.status === 404) { + + //a 404 indicates that the request was not found - this could be due to a non existing url, or it could + //be due to accessing a url with a parameter that doesn't exist, either way we should notifiy the user about it + + var errMsg = "The URL returned a 404 (not found):
" + originalResponse.config.url.split('?')[0] + ""; + if (originalResponse.data && originalResponse.data.ExceptionMessage) { + errMsg += "
with error:
" + originalResponse.data.ExceptionMessage + ""; + } + if (originalResponse.config.data) { + errMsg += "
with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information."; + } + + notifications.error( + "Request error", + errMsg); + + } + else if (originalResponse.status === 403) { + //if the status was a 403 it means the user didn't have permission to do what the request was trying to do. + //How do we deal with this now, need to tell the user somehow that they don't have permission to do the thing that was + //requested. We can either deal with this globally here, or we can deal with it globally for individual requests on the umbRequestHelper, + // or completely custom for services calling resources. + + //http://issues.umbraco.org/issue/U4-2749 + + //It was decided to just put these messages into the normal status messages. + + var msg = "Unauthorized access to URL:
" + originalResponse.config.url.split('?')[0] + ""; + if (originalResponse.config.data) { + msg += "
with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information."; + } + + notifications.error( + "Authorization error", + msg); + } + + return promise; + }); + }; + }]) + + .value('requestInterceptorFilter', function() { + return ["www.gravatar.com"]; + }) + + // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block. + .config(['$httpProvider', function ($httpProvider) { + $httpProvider.responseInterceptors.push('securityInterceptor'); }]); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/services/listviewprevaluehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/listviewprevaluehelper.service.js new file mode 100644 index 0000000000..70bba2d26a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/listviewprevaluehelper.service.js @@ -0,0 +1,61 @@ +/** + @ngdoc service + * @name umbraco.services.listViewPrevalueHelper + * + * + * @description + * Service for accessing the prevalues of a list view being edited in the inline list view editor in the doctype editor + */ +(function () { + 'use strict'; + + function listViewPrevalueHelper() { + + var prevalues = []; + + /** + * @ngdoc method + * @name umbraco.services.listViewPrevalueHelper#getPrevalues + * @methodOf umbraco.services.listViewPrevalueHelper + * + * @description + * Set the collection of prevalues + */ + + function getPrevalues() { + return prevalues; + } + + /** + * @ngdoc method + * @name umbraco.services.listViewPrevalueHelper#setPrevalues + * @methodOf umbraco.services.listViewPrevalueHelper + * + * @description + * Changes the current layout used by the listview to the layout passed in. Stores selection in localstorage + * + * @param {Array} values Array of prevalues + */ + + function setPrevalues(values) { + prevalues = values; + } + + + + var service = { + + getPrevalues: getPrevalues, + setPrevalues: setPrevalues + + }; + + return service; + + } + + + angular.module('umbraco.services').factory('listViewPrevalueHelper', listViewPrevalueHelper); + + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js index 7cfc79a083..430ed02242 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js @@ -1,3 +1,26 @@ +/** + * @ngdoc service + * @name umbraco.services.localizationService + * + * @requires $http + * @requires $q + * @requires $window + * @requires $filter + * + * @description + * Application-wide service for handling localization + * + * ##usage + * To use, simply inject the localizationService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
+ *    localizationService.localize("area_key").then(function(value){
+ *        element.html(value);
+ *    });
+ * 
+ */ + angular.module('umbraco.services') .factory('localizationService', function ($http, $q, eventsService, $window, $filter, userService) { @@ -78,7 +101,17 @@ angular.module('umbraco.services') return deferred.promise; }, - //helper to tokenize and compile a localization string + /** + * @ngdoc method + * @name umbraco.services.localizationService#tokenize + * @methodOf umbraco.services.localizationService + * + * @description + * Helper to tokenize and compile a localization string + * @param {String} value the value to tokenize + * @param {Object} scope the $scope object + * @returns {String} tokenized resource string + */ tokenize: function (value, scope) { if (value) { var localizer = value.split(':'); @@ -95,7 +128,17 @@ angular.module('umbraco.services') return value; }, - // checks the dictionary for a localized resource string + /** + * @ngdoc method + * @name umbraco.services.localizationService#localize + * @methodOf umbraco.services.localizationService + * + * @description + * Checks the dictionary for a localized resource string + * @param {String} value the area/key to localize + * @param {Array} tokens if specified this array will be sent as parameter values + * @returns {String} localized resource string + */ localize: function (value, tokens) { return service.initLocalizedResources().then(function (dic) { var val = _lookup(value, tokens, dic); 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 ccdd283ea6..a3d1e5b0c6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -170,16 +170,14 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ } } - else { - //return an error object including the error message for UI - deferred.reject({ - errorMsg: result.errorMsg, - data: result.data, - status: result.status - }); + //return an error object including the error message for UI + deferred.reject({ + errorMsg: result.errorMsg, + data: result.data, + status: result.status + }); - } }); @@ -266,15 +264,14 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ } } - else { - - //return an error object including the error message for UI - deferred.reject({ - errorMsg: 'An error occurred', - data: data, - status: status - }); - } + + //return an error object including the error message for UI + deferred.reject({ + errorMsg: 'An error occurred', + data: data, + status: status + }); + }); @@ -337,4 +334,4 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ } }; } -angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); \ No newline at end of file +angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 9fbf2947af..ba395becfc 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -1,4 +1,100 @@ /*Contains multiple services for various helper tasks */ +function versionHelper() { + + return { + + //see: https://gist.github.com/TheDistantSea/8021359 + versionCompare: function(v1, v2, options) { + var lexicographical = options && options.lexicographical, + zeroExtend = options && options.zeroExtend, + v1parts = v1.split('.'), + v2parts = v2.split('.'); + + function isValidPart(x) { + return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); + } + + if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { + return NaN; + } + + if (zeroExtend) { + while (v1parts.length < v2parts.length) { + v1parts.push("0"); + } + while (v2parts.length < v1parts.length) { + v2parts.push("0"); + } + } + + if (!lexicographical) { + v1parts = v1parts.map(Number); + v2parts = v2parts.map(Number); + } + + for (var i = 0; i < v1parts.length; ++i) { + if (v2parts.length === i) { + return 1; + } + + if (v1parts[i] === v2parts[i]) { + continue; + } + else if (v1parts[i] > v2parts[i]) { + return 1; + } + else { + return -1; + } + } + + if (v1parts.length !== v2parts.length) { + return -1; + } + + return 0; + } + }; +} +angular.module('umbraco.services').factory('versionHelper', versionHelper); + +function dateHelper() { + + return { + + convertToServerStringTime: function(momentLocal, serverOffsetMinutes, format) { + + //get the formatted offset time in HH:mm (server time offset is in minutes) + var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + + moment() + .startOf('day') + .minutes(Math.abs(serverOffsetMinutes)) + .format('HH:mm'); + + var server = moment.utc(momentLocal).utcOffset(formattedOffset); + return server.format(format ? format : "YYYY-MM-DD HH:mm:ss"); + }, + + convertToLocalMomentTime: function (strVal, serverOffsetMinutes) { + + //get the formatted offset time in HH:mm (server time offset is in minutes) + var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + + moment() + .startOf('day') + .minutes(Math.abs(serverOffsetMinutes)) + .format('HH:mm'); + + //convert to the iso string format + var isoFormat = moment(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset; + + //create a moment with the iso format which will include the offset with the correct time + // then convert it to local time + return moment.parseZone(isoFormat).local(); + } + + }; +} +angular.module('umbraco.services').factory('dateHelper', dateHelper); function packageHelper(assetsService, treeService, eventsService, $templateCache) { 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 42d50b1ce7..74eb872aa2 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js @@ -19,7 +19,7 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ { value: "assets/img/application/logo@3x.png" } ]; $scope.touchDevice = appState.getGlobalState("touchDevice"); - + $scope.removeNotification = function (index) { notificationsService.remove(index); @@ -28,12 +28,12 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ $scope.closeDialogs = function (event) { //only close dialogs if non-link and non-buttons are clicked var el = event.target.nodeName; - var els = ["INPUT","A","BUTTON"]; + var els = ["INPUT", "A", "BUTTON"]; - if(els.indexOf(el) >= 0){return;} + if (els.indexOf(el) >= 0) { return; } var parents = $(event.target).parents("a,button"); - if(parents.length > 0){ + if (parents.length > 0) { return; } @@ -49,31 +49,31 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ var evts = []; //when a user logs out or timesout - evts.push(eventsService.on("app.notAuthenticated", function() { + evts.push(eventsService.on("app.notAuthenticated", function () { $scope.authenticated = null; $scope.user = null; })); - + //when the app is read/user is logged in, setup the data evts.push(eventsService.on("app.ready", function (evt, data) { - + $scope.authenticated = data.authenticated; $scope.user = data.user; - updateChecker.check().then(function(update){ - if(update && update !== "null"){ - if(update.type !== "None"){ + updateChecker.check().then(function(update) { + if (update && update !== "null") { + if (update.type !== "None") { var notification = { - headline: "Update available", - message: "Click to download", - sticky: true, - type: "info", - url: update.url + headline: "Update available", + message: "Click to download", + sticky: true, + type: "info", + url: update.url }; notificationsService.add(notification); } } - }) + }); //if the user has changed we need to redirect to the root so they don't try to continue editing the //last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true) @@ -91,38 +91,35 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ if ($scope.user.emailHash) { //let's attempt to load the avatar, it might not exist or we might not have - // internet access so we'll detect it first - $http.get("https://www.gravatar.com/avatar/" + $scope.user.emailHash + ".jpg?s=64&d=404") + // internet access, well get an empty string back + $http.get(umbRequestHelper.getApiUrl("gravatarApiBaseUrl", "GetCurrentUserGravatarUrl")) .then( - function successCallback(response) { - $("#avatar-img").fadeTo(1000, 0, function () { - $scope.$apply(function () { - //this can be null if they time out - if ($scope.user && $scope.user.emailHash) { - var avatarBaseUrl = "https://www.gravatar.com/avatar/", - hash = $scope.user.emailHash; + function successCallback(response) { + // if we can't download the gravatar for some reason, an null gets returned, we cannot do anything + if (response.data !== "null") { + if ($scope.user && $scope.user.emailHash) { + var avatarBaseUrl = "https://www.gravatar.com/avatar/"; + var hash = $scope.user.emailHash; + + $scope.avatar = [ + { value: avatarBaseUrl + hash + ".jpg?s=30&d=mm" }, + { value: avatarBaseUrl + hash + ".jpg?s=60&d=mm" }, + { value: avatarBaseUrl + hash + ".jpg?s=90&d=mm" } + ]; + } + } - $scope.avatar = [ - { value: avatarBaseUrl + hash + ".jpg?s=30&d=mm" }, - { value: avatarBaseUrl + hash + ".jpg?s=60&d=mm" }, - { value: avatarBaseUrl + hash + ".jpg?s=90&d=mm" } - ]; - } - }); - $("#avatar-img").fadeTo(1000, 1); - }); }, function errorCallback(response) { //cannot load it from the server so we cannot do anything }); } - })); - evts.push(eventsService.on("app.ysod", function(name, error) { + evts.push(eventsService.on("app.ysod", function (name, error) { $scope.ysodOverlay = { view: "ysod", error: error, - show: true + show: true }; })); @@ -141,4 +138,4 @@ angular.module('umbraco').controller("Umbraco.MainController", MainController). config(function (tmhDynamicLocaleProvider) { //Set url for locale files tmhDynamicLocaleProvider.localeLocationPattern('lib/angular/1.1.5/i18n/angular-locale_{{locale}}.js'); - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js index 386e9a6712..54519f353b 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js @@ -100,7 +100,6 @@ function SearchController($scope, searchService, $log, $location, navigationServ //a canceler exists, so perform the cancelation operation and reset if (canceler) { - console.log("CANCELED!"); canceler.resolve(); canceler = $q.defer(); } diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index 4b732bf56e..5cb8e6230f 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -17,7 +17,7 @@ angular.module("umbraco.install").factory('installerService', function($rootScop //add to umbraco installer facts here var facts = ['Umbraco helped millions of people watch a man jump from the edge of space', - 'Over 300 000 websites are currently powered by Umbraco', + 'Over 370 000 websites are currently powered by Umbraco', "At least 2 people have named their cat 'Umbraco'", 'On an average day, more than 1000 people download Umbraco', 'umbraco.tv is the premier source of Umbraco video tutorials to get you started', @@ -56,7 +56,7 @@ angular.module("umbraco.install").factory('installerService', function($rootScop return (found) ? found.description : null; } - //calculates the offset of the progressbar on the installaer + //calculates the offset of the progressbar on the installer function calculateProgress(steps, next) { var sorted = _.sortBy(steps, "serverOrder"); diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html index 0cc7511f89..1b13768c9a 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html @@ -9,20 +9,16 @@
What type of database do you use? -
- -
+
+ +
-

Great!, no need to configure anything then, you simply click the continue button below to continue to the next step

+

Great!, no need to configure anything then, you simply click the continue button below to continue to the next step

- -
- What is the exact connectionstring we should use?
@@ -42,7 +38,7 @@
- + Enter server domain or IP
@@ -52,7 +48,7 @@
- Enter the name of the database @@ -61,49 +57,48 @@
-
- What credentials are used to access the database? -
-
- -
- - Enter the database user name -
-
-
- -
-
- -
- - Enter the database password -
-
-
- -
+ What credentials are used to access the database? +
+
+
- + + Enter the database user name
+
+ +
+
+ +
+ + Enter the database password +
+
+
+ +
+
+ +
+
-
+
- + diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html b/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html index 374772451b..5242fa8554 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html @@ -3,10 +3,17 @@

Welcome to the Umbraco installer. You see this screen because your Umbraco installation needs a quick upgrade of its database and files, which will ensure your website is kept as fast, secure and up to date as possible.

+ +

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

+

+ View Report +

+

Simply click continue below to be guided through the rest of the upgrade

-

diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html index 2cd50fe208..7be0d23959 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html @@ -10,14 +10,14 @@
-
- -
+
+ +
-
+
Your email will be used as your login
@@ -46,17 +46,15 @@
-
- - - + Customize
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 02f56e4046..9d1ec8cd32 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -118,6 +118,12 @@ body { } } +@media (max-width: 500px) { + #search-form .form-search { + width: ~"(calc(~'100%' - ~'80px'))"; + } +} + #navigation { left: 80px; top: 0; @@ -217,19 +223,17 @@ body { padding-left:20px } - - @media (max-width: 767px) { // make leftcolumn smaller on tablets #leftcolumn { - width: 60px; + width: 61px; } #applications ul.sections { - width: 60px; + width: 61px; } ul.sections.sections-tray { - width: 60px; + width: 61px; } #applications-tray { left: 60px; @@ -241,35 +245,24 @@ body { left: 30px; } #umbracoMainPageBody .umb-modal-left.fade.in { - margin-left: 60px; + margin-left: 61px; } } - - @media (max-width: 500px) { // make leftcolumn smaller on mobiles #leftcolumn { - width: 40px; + width: 41px; } #applications ul.sections { - width: 40px; - } - ul.sections li [class^="icon-"]:before { - font-size: 25px!important; - } - #applications ul.sections li.avatar a img { - width: 25px; - } - ul.sections a span { - display:none !important; + width: 41px; } #applications ul.sections-tray { - width: 40px; + width: 41px; } ul.sections.sections-tray { - width: 40px; + width: 41px; } #applications-tray { left: 40px; @@ -281,7 +274,7 @@ body { left: 20px; } #umbracoMainPageBody .umb-modal-left.fade.in { - margin-left: 40px; + margin-left: 41px; width: 85%!important; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 9d01cae4b9..2b53fbddfc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -60,6 +60,9 @@ @import "application/shadows.less"; @import "application/animations.less"; +// Utilities +@import "utilities/_font-weight.less"; + // Belle styles @import "buttons.less"; @import "forms.less"; @@ -108,6 +111,11 @@ @import "components/umb-empty-state.less"; @import "components/umb-property-editor.less"; @import "components/umb-iconpicker.less"; +@import "components/umb-packages.less"; +@import "components/umb-package-local-install.less"; +@import "components/umb-lightbox.less"; +@import "components/umb-avatar.less"; +@import "components/umb-progress-bar.less"; @import "components/buttons/umb-button.less"; @import "components/buttons/umb-button-group.less"; @@ -115,6 +123,12 @@ @import "components/notifications/umb-notifications.less"; @import "components/umb-file-dropzone.less"; +// Utilities +@import "utilities/_flexbox.less"; +@import "utilities/_spacing.less"; +@import "utilities/_text-align.less"; +@import "utilities/_width.less"; + //page specific styles @import "pages/document-type-editor.less"; @import "pages/login.less"; @@ -126,3 +140,5 @@ @import "typeahead.less"; @import "hacks.less"; + +@import "healthcheck.less"; 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 6d43286f2d..00dd8592ff 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -2,32 +2,42 @@ contains styling for all main editor directives */ -.umb-editor-wrapper{ +.umb-editor-wrapper { background: white; position: absolute; - top: 0px; bottom: 0px; left: 0px; right: 0px; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; + + > form { + display: flex; + flex-direction: column; + height: 100%; + } } - -.umb-editor-header{ +.umb-editor-header { background: @grayLighter; border-bottom: 1px solid @grayLight; - position: absolute; - height: 99px; - top: 0px; - right: 0px; - left: 0px; + flex: 0 0 99px; + position: relative; } +.umb-editor-header__actions-menu { + margin-left: auto; +} .umb-editor-container { - top: 101px; - left: 0px; - right: 0px; - bottom: 52px; - position: absolute; - clear: both; + position: relative; + top: 0; + right: 0; + bottom: 0; + left: 0; overflow: auto; + flex: 1 1 100%; } .umb-editor-wrapper.-no-footer .umb-editor-container { @@ -38,18 +48,12 @@ overflow: hidden; } -.umb-editor-drawer{ +.umb-editor-drawer { margin: 0; padding: 10px 20px; - z-index: 999; - position: absolute; - bottom: 0px; - left: 0px; - right: 0px; - height: 31px; - background: @grayLighter; border-top: 1px solid @grayLight; + flex: 1 0 31px; } 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 3a5367c7c9..0792925571 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 @@ -12,9 +12,9 @@ .umb-editor-sub-header.-umb-sticky-bar { box-shadow: 0 5px 0 rgba(0, 0, 0, 0.08), 0 1px 0 rgba(0, 0, 0, 0.16); transition: box-shadow 1s; - top: 100px; - transform: translate(0, 50%); - + top: 100px; /* height of header */ + margin-top: 0; + margin-bottom: 0; } .umb-group-builder__property-preview .umb-editor-sub-header { 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 0af0555b89..7a7ac15c6b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -133,7 +133,7 @@ bottom: 0; border: none; box-shadow: 0 0 20px rgba(0,0,0,0.19), 0 0 6px rgba(0,0,0,0.23); - margin-left: 80px; + margin-left: 81px; } .umb-overlay.umb-overlay-left .umb-overlay-header { @@ -148,7 +148,14 @@ @media (max-width: 767px) { .umb-overlay.umb-overlay-left { - margin-left: 60px; + margin-left: 61px; + } +} + +@media (max-width: 500px) { + .umb-overlay.umb-overlay-left { + margin-left: 41px; + width: ~"(calc(~'100%' - ~'41px'))"; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less new file mode 100644 index 0000000000..c4414c2880 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less @@ -0,0 +1,30 @@ +.umb-avatar { + border-radius: 50%; + width: 50px; + height: 50px; +} + +.umb-avatar.-xs { + width: 30px; + height: 30px; +} + +.umb-avatar.-s { + width: 40px; + height: 40px; +} + +.umb-avatar.-m { + width: 50px; + height: 50px; +} + +.umb-avatar.-l { + width: 70px; + height: 70px; +} + +.umb-avatar.-xl { + width: 100px; + height: 100px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less index 5854599722..d09946dfa3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less @@ -2,16 +2,22 @@ list-style: none; margin-bottom: 0; margin-left: 0; + display: flex; + flex-wrap: wrap; } .umb-breadcrumbs__ancestor { - display: inline-block; + display: flex; } .umb-breadcrumbs__ancestor-link, .umb-breadcrumbs__ancestor-text { - font-size: 11px; - color: #555; + font-size: 11px; + color: #555; + max-width: 150px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .umb-breadcrumbs__ancestor-link { 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 d4e82a6419..6b3c46d927 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 @@ -5,7 +5,7 @@ // tall and small version - animate height .dropzone { height: 400px; - width: 100%; + width: auto; padding: 50px 0; border: 1px dashed @grayLight; text-align: center; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less new file mode 100644 index 0000000000..9f1ef219a9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less @@ -0,0 +1,82 @@ +.umb-lightbox { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 999; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} + +.umb-lightbox__backdrop { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(21, 21, 23, 0.7); + width: 100%; + height: 100%; +} + +.umb-lightbox__close { + position: absolute; + top: 20px; + right: 60px; +} + +.umb-lightbox__close i { + font-size: 20px; + cursor: pointer; + height: 40px; + width: 40px; +} + +.umb-lightbox__images { + position: relative; + z-index: 1000; +} + +.umb-lightbox__image { + background: @white; + border-radius: 3px; + padding: 10px; + img { + max-width: 50vw; + max-height: 70vh; + } +} + +.umb-lightbox__control { + background-color: white; + width: 50px; + height: 50px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + position: absolute; +} + +.umb-lightbox__control.-next { + right: 20px; + top: 50%; + transform: translate(0, -50%); +} + +.umb-lightbox__control.-prev { + left: 20px; + top: 50%; + transform: translate(0, -50%); +} + +.umb-lightbox__control-icon { + color: @blue; + font-size: 20px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less index b2d03147a0..6443ec47e6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less @@ -7,6 +7,8 @@ left: 50%; transform: translate(-50%, -50%); font-size: 0; + margin-left: -6px; // hack to center it + margin-top: -6px; // hack to center it } .umb-load-indicator__bubble { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less new file mode 100644 index 0000000000..2a624b1c56 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less @@ -0,0 +1,135 @@ +/* + + Install local package + +*/ + +// Helpers +.faded { + color: @grayMed; +} + + + +.umb-upload-local__dropzone { + position: relative; + width: 500px; + height: 300px; + border: 2px dashed @grayLight; + border-radius: 3px; + background: @grayLighter; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + margin-bottom: 30px; + + transition: 100ms box-shadow ease, 100ms border ease; + + &:hover, + &.drag-over { + border-color: @blue; + border-style: solid; + box-shadow: 0 3px 8px rgba(0,0,0, .1); + transition: 100ms box-shadow ease, 100ms border ease; + } +} + +.umb-upload-local__dropzone i { + display: block; + color: @grayLight; + font-size: 110px; + line-height: 1; +} + +.umb-upload-local__select-file { + font-weight: bold; + color: @blue; + cursor: pointer; + + &:hover { + text-decoration: underline; + } +} + + + +// Accept terms +.umb-accept-terms { + display: flex; + align-items: center; + + font-size: 13px; +} + +.umb-package-installer-label .label-text { + margin-left: 5px; +} + +.umb-package-installer-label input[type="radio"], +.umb-package-installer-label input[type="checkbox"] { + margin-top: 0px; +} + +.umb-package-installer-label { + display: inline-flex; + align-items: center; + + font-size: 13px; + user-select: none; +} + + + +// Info state +.umb-info-local-items { + border: 2px solid @grayLight; + border-radius: 3px; + background: @grayLighter; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + margin: 0 20px; + + width: 100%; + max-width: 540px; +} + +.umb-info-local-items a { + text-decoration: underline; + + &:hover { + text-decoration: none; + } +} + + +.umb-info-local-items .umb-package-icon { + width: 100%; + box-sizing: border-box; + min-height: 150px; + font-size: 60px; +} + +.umb-info-local-items .umb-package-icon img { + max-width: 100px; +} + +.umb-info-local-items .umb-package-info { + width: 100%; + box-sizing: border-box; + padding: 20px 40px; +} + +.umb-info-local-item { + margin-bottom: 20px; +} + +.umb-upload-local__dropzone .umb-info-local-item { + margin:20px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less new file mode 100644 index 0000000000..f14df918c0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less @@ -0,0 +1,630 @@ +.umb-packages-view-title { + font-size: 20px; + font-weight: bold; + color: @black; + margin-bottom: 30px; +} + +.umb-packages-view-wrapper { + padding: 20px 60px; +} + +@media (max-width: 768px) { + + .umb-packages-view-wrapper { + padding: 0; + } + +} + +.umb-packages-section { + margin-bottom: 40px; +} + +.umb-packages-search { + width: 100%; + background: @grayLighter; + border-radius: 3px; + padding: 30px; + box-sizing: border-box; +} + +.umb-packages-search input { + border-width: 2px; + border-radius: 3px; + min-height: 44px; + + padding: 4px 10px; + font-size: 16px; + border-color: #ececec; + margin-bottom: 0; + border-color: @grayLight; + + &:hover, &:focus { + border-color: @grayLight; + } +} + +.umb-packages__pagination { + display: flex; + justify-content: center; +} + +.umb-packages { + margin: 0 -10px; + display: flex; + flex-wrap: wrap; +} + +// Cards +.umb-package { + padding: 10px; + box-sizing: border-box; + flex: 0 0 100%; + max-width: 100%; +} + +@media (min-width: 768px) { + .umb-package { + flex: 0 0 50%; + max-width: 50%; + } +} + +@media (min-width: 1200px) { + .umb-package { + flex: 0 0 33.33%; + max-width: 33.33%; + } +} + +@media (min-width: 1400px) { + .umb-package { + flex: 0 0 25%; + max-width: 25%; + } +} + +@media (min-width: 1700px) { + .umb-package { + flex: 0 0 20%; + max-width: 20%; + } +} + + +@media (min-width: 1900px) { + .umb-package { + flex: 0 0 16.66%; + max-width: 16.66%; + } +} + +@media (min-width: 2200px) { + .umb-package { + flex: 0 0 14.28%; + max-width: 14.28%; + } +} + +.umb-package-link { + display: block; + flex-wrap: wrap; + flex-direction: column; + justify-content: center; + + position: relative; + box-sizing: border-box; + + height: 100%; + width: 100%; + + border: 1px solid #ececec; + border-radius: 3px; + + text-decoration: none !important; + + transition: border-color 100ms ease; + + &:hover { + border-color: @blue; + } +} + + + +// Icon +.umb-package-icon { + display: flex; + + justify-content: center; + align-items: center; + + padding-top: 10px; + padding-right: 10px; + padding-left: 10px; + padding-bottom: 10px; + + text-align: center; + background-color: white; + + border-top-right-radius: 3px; + border-top-left-radius: 3px; + + min-height: 60px; +} + +.umb-package-icon img { + max-width: 70px; + width: 70px; + height: auto; +} + + +// Info +.umb-package-info { + padding-right: 15px; + padding-bottom: 15px; + padding-left: 15px; + padding-top: 15px; + text-align: center; + background: @grayLighter; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + + border-top: 1px solid #ececec; +} + + +// Name +.umb-package-name { + font-size: 14px; + max-width: 250px; + margin-bottom: 5px; + + font-weight: bold; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + line-height: normal; + margin-left: auto; + margin-right: auto; +} + +.umb-package-description { + font-size: 11px; + color: @grayMed; + word-wrap: break-word; + line-height: 1.1rem; + + white-space: nowrap; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; +} + +// Numbers +.umb-package-numbers { + display: flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: center; + + opacity: .6; + + margin-top: 10px; +} + +.umb-package-numbers small { + padding: 0 5px; + + display: flex; + align-items: center; + justify-content: center; +} + +.umb-package-numbers i { + font-size: 14px; +} + +.umb-package-link:hover .umb-package-numbers { + opacity: 1; +} + +.umb-package-link:hover .umb-package-numbers .icon-hearts { + color: red !important; +} + +// Version +.umb-package-version { + display: inline-flex; + font-size: 11px; + font-weight: bold; + padding: 1px 5px; + background: #ececec; + border-radius: 3px; + color: black; +} + +/* umb-buttons-era */ +.umb-era-button { + display: flex; + justify-content: center; + align-items: center; + + font-size: 14px; + font-weight: bold; + + height: 38px; + line-height: 1; + + max-width: 100%; + padding: 0 18px; + + color: #484848; + background-color: #e0e0e0; + + text-decoration: none !important; + user-select: none; + + white-space: nowrap; + overflow: hidden; + + border-radius: 3px; + border: 0 none; + box-sizing: border-box; + + cursor: pointer; + + transition: background-color 80ms ease, color 80ms ease; +} + + +.umb-era-button:hover, +.umb-era-button:active { + color: #484848; + background-color: #d3d3d3; + outline: none; + text-decoration: none; +} + + +.umb-era-button:focus { + outline: none; +} + +.umb-era-button.-blue { + background: @blue; + color: white; +} + +.umb-era-button.-blue:hover { + background-color: @blueDark; +} + +.umb-era-button.-link { + padding: 0; + background: transparent; +} + +.umb-era-button.-link:hover { + background-color: transparent; + opacity: .6; +} + +.umb-era-button.-inactive { + cursor: not-allowed; + color: #BBB; + background: #EAE7E7; +} + +.umb-era-button.-inactive:hover { + color: #BBB; + background: #EAE7E7; +} + + +.umb-era-button.-full-width { + display: block; + width: 100%; +} + +.umb-era-button.umb-button--s { + height: 30px; + font-size: 13px; +} + + +/* CATEGORIES */ + +.umb-packages-categories { + display: flex; + user-select: center; + flex-wrap: wrap; +} + +.umb-packages-category { + display: flex; + align-items: center; + flex: 1 0 auto; + justify-content: center; + max-width: 25%; + font-size: 14px; + font-weight: bold; + color: @black; + box-sizing: border-box; + justify-content: center; + border-top: 1px solid @grayLight; + border-bottom: 1px solid @grayLight; + border-right: 1px solid @grayLight; + padding: 10px 0; +} + + +@media (max-width: 768px) { + .umb-packages-category { + width: 100%; + margin-top: 0; + margin-bottom: 15px !important; + margin-left: 0 !important; + margin-right: 0 !important; + } +} + +@media (max-width: 992px) { + .umb-packages-category { + border: 1px solid @grayLight; + margin: 5px; + flex: 0 0 auto; + + text-align: center; + padding: 10px; + + max-width: 100%; + + border-radius: 3px; + } +} + +@media (min-width: 1100px) and (max-width: 1300px) { + .umb-packages-category { + border: 1px solid @grayLight; + margin: 5px; + flex: 0 0 auto; + + text-align: center; + padding: 10px; + + max-width: 100%; + + border-radius: 3px; + } +} + + +.umb-packages-category:hover, +.umb-packages-category.-active { + text-decoration: none; + color: @blue; +} + +.umb-packages-category.-first { + border-left: 1px solid @grayLight; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} + +.umb-packages-category.-last { + border-right: 1px solid @grayLight; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +/* PACKAGE DETAILS */ + +.umb-package-details { + display: flex; +} + +a.umb-package-details__back-link { + font-weight: bold; + color: @black; +} + +.umb-package-details__back-link:hover { + color: @grayMed; + text-decoration: none; +} + + +@sidebarwidth: 350px; // Width of sidebar. Ugly hack because of old version of Less + +.umb-package-details__main-content { + flex: 1 1 auto; + margin-right: 40px; + width: ~"(calc(~'100%' - ~'@{sidebarwidth}' - ~'40px'))"; // Make sure that the main content area doesn't gets affected by inline styling +} + +.umb-package-details__sidebar { + flex: 0 0 @sidebarwidth; +} + +@media (max-width: 768px) { + + .umb-package-details { + flex-direction: column; + } + + .umb-package-details__main-content { + flex: 1 1 auto; + width: 100%; + margin-bottom: 30px; + margin-right: 0; + } + + .umb-package-details__sidebar { + flex: 1 1 auto; + width: 100%; + } +} + +.umb-package-details__section { + background: @grayLighter; + padding: 20px; + margin-bottom: 20px; + border-radius: 3px; +} + +.umb-package-details__section-title { + font-size: 17px; + font-weight: bold; + color: black; + margin-top: 0; + margin-bottom: 15px; +} + +.umb-package-details__section-description { + font-size: 12px; + line-height: 1.6em; + margin-bottom: 15px; +} + +.umb-package-details__information-item { + margin-bottom: 10px; + font-size: 13px; +} + +.umb-package-details__information-item-label { + color: black; + font-weight: bold; +} + +.umb-package-details__information-item-content { + word-break: break-word; +} + +.umb-package-details__information-item-label-2 { + font-size: 12px; + color: @grayMed; +} + +.umb-package-details__compatability { + margin-bottom: 15px; +} + +.umb-package-details__compatability-label { + margin-bottom: 3px; +} + +.umb-package-details__description { + margin-bottom: 20px; + line-height: 1.6em; +} + +.umb-package-details__description p { + margin-bottom: 20px; +} + +/* Links */ + +.umb-package-details__link { + color: #DA3287; +} + +.umb-package-details__link:hover { + color: #B32D71; + text-decoration: none; +} + +/* Owner profile */ + +.umb-package-details__owner-profile { + display: flex; + align-items: center; +} +.umb-package-details__owner-profile-avatar { + margin-right: 15px; + flex: 0 0 auto; +} + +.umb-package-details__owner-profile-name { + font-size: 15px; + color: #000000; + font-weight: bold; +} + +.umb-package-details__owner-profile-karma { + font-size: 12px; + color: @grayMed; +} + +/* gallery */ + +.umb-gallery__thumbnails { + display: flex; + flex-wrap: wrap; +} + +.umb-gallery__thumbnail { + flex: 0 1 100px; + border: 1px solid @grayLight; + border-radius: 3px; + margin: 5px; + padding: 10px; + box-sizing: border-box; + max-width: 100px; +} + +.umb-gallery__thumbnail:hover { + cursor: pointer; + border-color: @blue; +} + +/* PACKAGE LIST */ + +.umb-package-list { + display: flex; + flex-direction: column; +} + +.umb-package-list__item { + display: flex; + flex-direction: row; + background: @grayLighter; + margin-bottom: 10px; + border-radius: 3px; + padding: 20px; + align-items: center; +} + +.umb-package-list__item-icon { + flex: 0 0 50px; + margin-right: 20px; + font-size: 30px; + text-align: center; +} + +.umb-package-list__item-content { + flex: 1 1 auto; + margin-right: 20px; +} + +.umb-package-list__item-name { + font-size: 16px; + margin-bottom: 5px; + color: @black; + font-weight: bold; +} + +.umb-package-list__item-description { + font-size: 14px; + color: @grayMed; +} + +.umb-package-list__item-actions { + flex: 1 1 auto; + display: flex; + justify-content: flex-end; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-bar.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-bar.less new file mode 100644 index 0000000000..e7d54bca59 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-bar.less @@ -0,0 +1,20 @@ +.umb-progress-bar { + background: @grayLight; + width: 100%; + display: block; + height: 10px; + border-radius: 10px; + box-sizing: border-box; + position: relative; + overflow: hidden; +} + +.umb-progress-bar__progress { + background: #50C878; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 100%; + border-radius: 10px; +} 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 874f9e9551..219c15c7d5 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 @@ -3,6 +3,8 @@ display: flex; flex-direction: column; + position: relative; + border: 1px solid @grayLight; flex-wrap: nowrap; @@ -11,6 +13,25 @@ min-width: 640px; } +.umb-table.umb-table-inactive { + + &:before { + content: ""; + background: rgba(255, 255, 255, 0.75); + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + z-index: 10; + + outline: 1px solid rgba(255, 255, 255, 0.75); + } + +} + .umb-table a { text-decoration: none; cursor: pointer; @@ -93,6 +114,14 @@ input.umb-table__input { } } +.umb-table-body .umb-table-row.-solid { + cursor: default; + + &:hover { + background-color: white; + } +} + .umb-table-body__link { text-decoration: none; @@ -183,6 +212,8 @@ input.umb-table__input { user-select: none; } + + .umb-table-row.-selected, .umb-table-row.-selected:hover { background-color: fade(@blueDark, 4%); diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index f9dedf5902..d046ac7104 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -9,7 +9,7 @@ small.umb-detail, label small, .guiDialogTiny { - color: #b3b3b3 !important; + color: @grayMed !important; text-decoration: none; display: block; font-weight: normal; diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less index fb809af015..716cb40256 100644 --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less @@ -173,3 +173,49 @@ iframe, .content-column-body { .pa-form + .pa-form { margin-top: 10px; } + + +// The below adds a default selector to all pre elements to ensure that styles are applied +// without having to add the class ".code" to the element. Styles have been created by +// combining the various declarations from the Bootstrap code.less file and fixing some mistakes +// in Bootstrap 2. +// This fixes issues with the markdown editor preview and should not cause issues with any other editor. + +// Inline code +// 1: Revert border radius to match look and feel of 7.4+ +code{ + .border-radius(@baseBorderRadius); // 1 +} + +// Blocks of code +// 1: Wrapping code is unreadable on small devices. +pre { + display: block; + padding: (@baseLineHeight - 1) / 2; + margin: 0 0 @baseLineHeight / 2; + #font > #family > .monospace; + font-size: @baseFontSize - 1; // 14px to 13px + color: @grayDark; + line-height: @baseLineHeight; + white-space: pre; // 1 + overflow-x: auto; // 1 + background-color: #f5f5f5; + border: 1px solid #ccc; // fallback for IE7-8 + border: 1px solid rgba(0,0,0,.15); + .border-radius(@baseBorderRadius); + + + // Make prettyprint styles more spaced out for readability + &.prettyprint { + margin-bottom: @baseLineHeight; + } + + // Account for some code outputs that place code tags in pre tags + code { + padding: 0; + white-space: pre; // 1 + word-wrap: normal; // 1 + background-color: transparent; + border: 0; + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less new file mode 100644 index 0000000000..96b8a611db --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less @@ -0,0 +1,338 @@ + +.umb-healthcheck { + display: flex; + flex-wrap: wrap; + margin-left: -10px; + margin-right: -10px; +} + +.umb-healthcheck-help-text { + line-height: 1.6em; + margin-bottom: 30px; +} + +.umb-healthcheck-action-bar { + display: flex; + justify-content: flex-end; + margin-bottom: 20px; +} + + +/* Group and states */ +.umb-healthcheck-group { + display: flex; + flex-wrap: wrap; + flex-direction: column; + + background: @grayLighter; + border-radius: 3px; + + padding: 15px 10px; + box-sizing: border-box; + + text-align: center; + border: 2px solid transparent; + height: 100%; +} + +.umb-healthcheck-group:hover { + border: 2px solid @blue; + cursor: pointer; +} + +.umb-healthcheck-group__load-container { + position: relative; + height: 30px; + margin-top: 15px; + margin-bottom: 16px; +} + + +/* Title */ +.umb-healthcheck-title { + font-size: 14px; + font-weight: bold; +} + + +/* Messages */ +.umb-healthcheck-messages { + margin-top: 15px; +} + +.umb-healthcheck-message { + position: relative; + background: #fff; + border-radius: 50px; + display: inline-flex; + align-items: center; + padding-left: 8px; + padding-right: 8px; + margin-bottom: 5px; + color: #000; + font-weight: bold; + font-size: 13px; +} + +.umb-healthcheck-message i { + font-size: 15px; + margin-right: 3px; +} + +.umb-healthcheck-details-link { + color: @blue; +} + +.umb-healthcheck-details-link:hover { + text-decoration: none; + color: @blue; +} + + +/* Helpers */ +.align-self-center { + align-self: center; +} + + +/* umb-buttons-era */ +.umb-era-button { + display: flex; + justify-content: center; + align-items: center; + + font-size: 14px; + font-weight: bold; + + height: 40px; + line-height: 1; + + max-width: 100%; + padding: 0 15px; + + color: #484848; + background-color: #e0e0e0; + + text-decoration: none !important; + user-select: none; + + white-space: nowrap; + overflow: hidden; + + border-radius: 3px; + border: 0 none; + box-sizing: border-box; + + cursor: pointer; + + transition: background-color 80ms ease, color 80ms ease, opacity 80ms ease; +} + + +.umb-era-button:hover, +.umb-era-button:active { + color: #484848; + background-color: #d3d3d3; + outline: none; + text-decoration: none; +} + + +.umb-era-button:focus { + outline: none; +} + +.umb-era-button.-blue { + background: @blue; + color: white; +} + +.umb-era-button.-blue:hover { + background-color: @blueDark; +} + +.umb-era-button.-link { + padding: 0; + background: transparent; +} + +.umb-era-button.-link:hover { + background-color: transparent; + opacity: .6; +} + +.umb-era-button.-inactive { + cursor: not-allowed; + color: #BBB; + background: #EAE7E7; +} + +.umb-era-button.-inactive:hover { + color: #BBB; + background: #EAE7E7; +} + + +.umb-era-button.-full-width { + display: block; + width: 100%; +} + +.umb-era-button.-white { + background-color: @white; + + &:hover { + opacity: .9; + } +} + +.umb-era-button.-text-black { + color: @black; +} + + +/* Spacing for boxes */ +.umb-air { + flex: 0 0 auto; + flex-basis: 100%; + max-width: 100%; + + padding: 10px; + box-sizing: border-box; + + @media (min-width: 500px) { + flex-basis: 50%; + max-width: 50%; + } + + @media (min-width: 768px) { + flex-basis: 20%; + max-width: 20%; + } +} + + +/* DETAILS */ + +.umb-healthcheck-back-link { + font-weight: bold; + color: @black; +} + +.umb-healthcheck-group__details { + border-radius: 3px; + margin-bottom: 40px; +} + +.umb-healthcheck-group__details-group-title { + background-color: @blue; + padding: 10px 20px; + display: flex; + align-items: center; + justify-content: space-between; + border-radius: 3px 3px 0 0; +} + +.umb-healthcheck-group__details-group-name { + font-size: 16px; + color: @white; + font-weight: bold; +} + +.umb-healthcheck-group__details-checks { + border: 1px solid @grayLight; + border-top: none; + border-radius: 0 0 3px 3px; +} + +.umb-healthcheck-group__details-check { + position: relative; +} + +.umb-healthcheck-group__details-check-title { + padding: 15px 20px; + background-color: @grayLighter; +} + +.umb-healthcheck-group__details-check-name { + font-size: 14px; + color: @black; + font-weight: bold; + margin-bottom: 2px; +} + +.umb-healthcheck-group__details-check-description { + font-size: 12px; + color: @grayMed; + line-height: 1.6em; +} + +.umb-healthcheck-group__details-status { + padding: 15px 0; + display: flex; + border-bottom: 2px solid @grayLighter; +} + +.umb-healthcheck-group__details-status-overlay { + background: @white; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + opacity: 0.9; +} + +.umb-healthcheck-group__details-status:last-child { + border-bottom: none; +} + +.umb-healthcheck-group__details-status-icon-container { + flex: 0 0 50px; + display: flex; + justify-content: center; + padding: 0 20px; +} + +.umb-healthcheck-status-icon { + font-size: 20px; + margin-top: 2px; +} + +.umb-healthcheck-status-icon.-large { + width: 70px; + height: 70px; + font-size: 30px; + background-color: @white; +} + +.umb-healthcheck-group__details-status-content { + padding: 0 20px; + flex: 1 1 auto; +} + +.umb-healthcheck-group__details-status-text { + line-height: 1.6em; +} + +.umb-healthcheck-group__details-status-actions { + display: flex; + flex-direction: column; + margin-top: 10px; +} + +.umb-healthcheck-group__details-status-action { + background-color: @grayLighter; + padding: 10px; + margin-bottom: 10px; + border-radius: 3px; +} + +.umb-healthcheck-group__details-status-action:last-child { + margin-bottom: 0; +} + +.umb-healthcheck-group__details-status-action-description { + margin-top: 5px; + font-size: 11px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index 88762edf0c..95c8a49b36 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -166,51 +166,6 @@ background: none } -.umb-listview .pagination { - margin: 0; -} - -.umb-listview .table th { - font-weight: normal -} - -.umb-listview .showing { - padding: 8px 4px 2px 4px; - background: none; - font-size: 11px; - color: #b0b0b0 -} - -.umb-listview .pagination { - text-align: center; -} - -.umb-listview .pagination ul { - -webkit-border-radius: 0px; - -moz-border-radius: 0px; - border-radius: 0px; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0); - -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0); - background: none -} - -.umb-listview .pagination ul > li > a, .pagination ul > li > span { - border:none; - padding: 8px 4px 2px 4px; - background: none; - font-size: 12px; - color: #b0b0b0 -} - -.umb-listview .pagination ul > li.active > a, .umb-listview .pagination ul > li > a:hover { - color: @black; -} - -.umb-listview .pagination ul > li.disabled > a, .umb-listview .pagination ul > li.disabled > a:hover { - color: @grayLight; -} - /* TEMP */ .umb-listview .table-striped tbody td { diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 0c2c2f07ad..8ac5af175c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -114,7 +114,7 @@ .umbracoDialog{ width: auto !Important; height: auto !Important; - padding: 20px; + padding: 20px; } .umbracoDialog .umb-pane{margin-left: 0px; margin-right: 0px; margin-top: 0px;} .umbracoDialog .umb-dialog-body .umb-pane{margin-left: 20px; margin-right: 20px; margin-top: 20px;} @@ -125,7 +125,11 @@ .umb-modal .controls-row{margin-left: 0px !important;} /* modal and umb-modal are used for right.hand dialogs */ -.modal.fade.in{border: none !important; border-radius: none !important;} +.modal { + border-radius: 0 !important; + + &.fade.in{border: none !important;} +} .umb-modal.fade { outline: none; top: 0 !important; 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 6805783a86..892977180e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -13,7 +13,9 @@ left: 0; margin: 0 !important; padding: 0; + border: none; border-radius: 0; + overflow-y: auto; } @@ -50,7 +52,7 @@ } .login-overlay .form { - position:fixed; + position:relative; display: block; top: 100px; left: 165px; @@ -78,11 +80,30 @@ margin-top: 10px; } +@media (max-width: 767px) and (max-height: 420px) and (orientation: landscape) { + // Move form closer to top on narrow screen sizes + .login-overlay .form { + top: 50px; + } +} + @media (max-width: 565px) { // Remove padding on login-form on smaller devices .login-overlay .form { + top: 60px; + right: 25px; left: inherit; - right:25px; + padding-left: 25px; + padding-right:25px; + width: auto; + } +} + +@media (max-width: 339px) { + .login-overlay .form { + input[type="text"], input[type="password"] { + width: 250px; + } } } diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index a1776bf12d..c80f72921c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -286,14 +286,14 @@ // form styles .umb-dialog .muted, .umb-panel .muted { - color: @grayLight; + color: @grayMed; } .umb-dialog a.muted:hover, .umb-dialog a.muted:focus, .umb-panel a.muted:hover, .umb-panel a.muted:focus { - color: darken(@grayLight, 10%); + color: darken(@grayMed, 10%); } .umb-dialog .text-warning, @@ -366,17 +366,17 @@ flex: 1; } -.umb-panel-header-content.-top-position { - position: relative; - top: -12px; -} - .umb-panel-header-left-side { display: flex; flex: 1; flex-direction: row; } +.umb-panel-header-left-side.-top-position { + position: relative; + top: -12px; +} + .umb-panel-header-icon { cursor: pointer; margin-right: 5px; @@ -483,13 +483,15 @@ input.umb-panel-header-description { .umb-editor-drawer-content { display: flex; align-items: center; - //justify-content: space-between; } .umb-editor-drawer-content__right-side { margin-left: auto; + flex: 0 0 auto; + padding-left: 10px; } .umb-editor-drawer-content__left-side { margin-right: auto; + padding-right: 10px; } 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 a06484b8a0..16d48807eb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -201,6 +201,15 @@ ul.color-picker li a { margin: 5px; background: white; border: 1px solid #f8f8f8; + + max-width: 100%; +} + + +.umb-mediapicker .umb-sortable-thumbnails li { + flex-direction: column; + margin: 0; + padding: 5px; } @@ -289,7 +298,7 @@ ul.color-picker li a { } .umb-cropper img { - max-width: initial; + max-width: none; } .umb-cropper .overlay, .umb-cropper-gravity .overlay { @@ -384,6 +393,7 @@ ul.color-picker li a { } .umb-cropper-gravity .viewport, .umb-cropper-gravity, .umb-cropper-imageholder { display: inline-block; + max-width: 100%; } .umb-cropper-imageholder { @@ -694,6 +704,31 @@ ul.color-picker li a { padding-top: 27px; } +.umb-fileupload .file-icon { + text-align: center; + display: block; + position: relative; + padding: 5px 0; + + > .icon { + font-size: 70px; + line-height: 110%; + color: #666; + text-align: center; + } + + > span { + color: #fff; + background: #666; + padding: 1px 3px; + font-size: 12px; + line-height: 130%; + position: absolute; + top: 45px; + left: 110px; + } +} + .umb-fileupload input { font-size: 12px; line-height: 1; diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index c2db58a99e..c764280c8d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -18,10 +18,12 @@ ul.sections li { transition: all .3s linear; } -ul.sections li [class^="icon-"]:before, -ul.sections li [class*=" icon-"]:before, +ul.sections li [class^="icon-"], +ul.sections li [class*=" icon-"], ul.sections li img.icon-section { font-size: 30px; + line-height: 20px; /* set line-height to ensure all icons use same line-height */ + display: inline-block; margin: 1px 0 0 0; opacity: 0.4; -webkit-transition: all .3s linear; @@ -29,8 +31,8 @@ ul.sections li img.icon-section { transition: all .3s linear; } -ul.sections:hover li [class^="icon-"]:before, -ul.sections:hover li [class*=" icon-"]:before, +ul.sections:hover li [class^="icon-"], +ul.sections:hover li [class*=" icon-"], ul.sections:hover li img.icon-section { opacity: 1 } @@ -109,32 +111,43 @@ ul.sections li.help { bottom: 0; left: 0; display: block; - width: 100%; + width: ~"(calc(~'100%' - ~'5px'))"; //subtract 4px orange border + 1px border-right for sections } ul.sections li.help a { border-bottom: none; } +@media (max-width: 500px) { + ul.sections li [class^="icon-"], + ul.sections li [class*=" icon-"] { + font-size: 25px; + } + ul.sections li:not(.avatar) a { + padding-top: 12px; + padding-bottom: 6px; + + .icon, .icon-section { + display: inline-block; + padding-left: 2px; + } + } + ul.sections a span { + display:none; + } +} + // Section slide-out tray for additional apps // ------------------------- -li.expand a, li.expand{border: none !Important;} +li.expand a, li.expand{border: none !important;} li.expand { + > a > i.icon { + transition: all .3s linear; + } &.open > a > i.icon { - -webkit-transition: all 1s !important; - -o-transition: all 1s !important; - -moz-transition: all 1s !important; - transition: all 1s !important; - - &:before { - -ms-transform: rotate(180deg) !important; /* IE 9 */ - -webkit-transform: rotate(180deg) !important; /* Chrome, Safari, Opera */ - -o-transform: rotate(180deg) !important; - -moz-transform: rotate(180deg) !important; - transform: rotate(180deg) !important; - } + transform: rotate(180deg); } } diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less index 86c9e4d081..d809418f2c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/tree.less @@ -208,7 +208,9 @@ content: "\e165"; } -.umb-tree .umb-tree-node-checked i { +.umb-tree .umb-tree-node-checked i[class^="icon-"], +.umb-tree .umb-tree-node-checked i[class*=" icon-"] { + font-family: 'icomoon' !important; color:@blue !important; } .umb-tree .umb-tree-node-checked i:before { diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_flexbox.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_flexbox.less new file mode 100644 index 0000000000..829aba08f7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_flexbox.less @@ -0,0 +1,43 @@ +/* + Flexbox +*/ + + +.flex { display: flex } + +.flex-column { flex-direction: column } +.flex-wrap { flex-wrap: wrap } + +.items-start { align-items: flex-start } +.items-end { align-items: flex-end } +.items-center { align-items: center } +.items-baseline { align-items: baseline } +.items-stretch { align-items: stretch } + +.self-start { align-self: flex-start } +.self-end { align-self: flex-end } +.self-center { align-self: center } +.self-baseline { align-self: baseline } +.self-stretch { align-self: stretch } + +.justify-start { justify-content: flex-start } +.justify-end { justify-content: flex-end } +.justify-center { justify-content: center } +.justify-between { justify-content: space-between } +.justify-around { justify-content: space-around } + +.content-start { align-content: flex-start } +.content-end { align-content: flex-end } +.content-center { align-content: center } +.content-between { align-content: space-between } +.content-around { align-content: space-around } +.content-stretch { align-content: stretch } + +/* 1. Fix for Chrome 44 bug. https://code.google.com/p/chromium/issues/detail?id=506893 */ +.flex-auto { + flex: 1 1 auto; + min-width: 0; /* 1 */ + min-height: 0; /* 1 */ +} + +.flex-none { flex: none } diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_font-weight.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_font-weight.less new file mode 100644 index 0000000000..111ed4b2ef --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_font-weight.less @@ -0,0 +1,10 @@ +/* + FONT WEIGHT +*/ + + + +.light { font-weight: 300; } +.normal { font-weight: 500; } +.semi-bold { font-weight: 600; } +.bold { font-weight: 700; } diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less new file mode 100644 index 0000000000..64d86d7b6f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less @@ -0,0 +1,54 @@ +/* + + Spacing + +*/ + +@spacing-none: 0; +@spacing-extra-small: .25rem; +@spacing-small: .5rem; +@spacing-medium: 1rem; +@spacing-large: 2rem; +@spacing-extra-large: 4rem; +@spacing-extra-extra-large: 8rem; +@spacing-extra-extra-extra-large: 16rem; + +/* + SPACING + An eight step powers of two scale ranging from 0 to 16rem. + Namespaces are composable and thus highly grockable - check the legend below + Legend: + p = padding + m = margin + a = all + h = horizontal + v = vertical + t = top + r = right + b = bottom + l = left + 0 = none + 1 = 1st step in spacing scale + 2 = 2nd step in spacing scale + 3 = 3rd step in spacing scale + 4 = 4th step in spacing scale + 5 = 5th step in spacing scale + 6 = 6th step in spacing scale + 7 = 7th step in spacing scale +*/ + + +.m-center { + margin-left: auto; + margin-right: auto; +} + + +.mt0 { margin-top: @spacing-none; } +.mt1 { margin-top: @spacing-extra-small; } +.mt2 { margin-top: @spacing-small; } +.mt3 { margin-top: @spacing-medium; } +.mt4 { margin-top: @spacing-large; } +.mt5 { margin-top: @spacing-extra-large; } +.mt6 { margin-top: @spacing-extra-extra-large; } +.mt7 { margin-top: @spacing-extra-extra-extra-large; } diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_text-align.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_text-align.less new file mode 100644 index 0000000000..beff81d80c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_text-align.less @@ -0,0 +1,7 @@ +/* + TEXT ALIGN +*/ + +.tl { text-align: left; } +.tr { text-align: right; } +.tc { text-align: center; } diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_width.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_width.less new file mode 100644 index 0000000000..a9f2fd3362 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_width.less @@ -0,0 +1,26 @@ +/* + Width +*/ + + +/* Width Scale */ + +.w1 { width: 1rem; } +.w2 { width: 2rem; } +.w3 { width: 4rem; } +.w4 { width: 8rem; } +.w5 { width: 16rem; } + +.w-10 { width: 10%; } +.w-20 { width: 20%; } +.w-25 { width: 25%; } +.w-33 { width: 33%; } +.w-34 { width: 34%; } +.w-40 { width: 40%; } +.w-50 { width: 50%; } +.w-60 { width: 60%; } +.w-75 { width: 75%; } +.w-80 { width: 80%; } +.w-100 { width: 100%; } + +.w-auto { width: auto; } diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 5bddbd8022..8ebb0f08ef 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -14,7 +14,7 @@ @grayDarker: #222; @grayDark: #343434; @gray: #555; -@grayMed: #999; +@grayMed: #7f7f7f; @grayLight: #d9d9d9; @grayLighter: #f8f8f8; @white: #fff; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index c38c55fcfb..aa037b7431 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -79,7 +79,7 @@ } $scope.loginSubmit = function (login, password) { - + //if the login and password are not empty we need to automatically // validate them - this is because if there are validation errors on the server // then the user has to change both username & password to resubmit which isn't ideal, @@ -121,13 +121,18 @@ $scope.requestPasswordResetSubmit = function (email) { - $scope.errorMsg = ""; + if (email && email.length > 0) { + $scope.requestPasswordResetForm.email.$setValidity('auth', true); + } + $scope.showEmailResetConfirmation = false; if ($scope.requestPasswordResetForm.$invalid) { return; } + $scope.errorMsg = ""; + authResource.performRequestPasswordReset(email) .then(function () { //remove the email entered diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 12f25f42a5..5933848c36 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -25,7 +25,7 @@ id="{{login.authType}}" name="provider" value="{{login.authType}}" title="Log in using your {{login.caption}} account"> - Sign in with {{login.caption}} + Sign in with {{login.caption}}
@@ -33,7 +33,7 @@

-
Or
+
or
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/rteembed.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/rteembed.controller.js index f2052ccc65..0c1cb5b62e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/rteembed.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/rteembed.controller.js @@ -32,7 +32,7 @@ break; case 1: //error - $scope.form.info = "Computer says no"; + $scope.form.info = "Could not embed media - please ensure the URL is valid"; break; case 2: $scope.form.preview = data.Markup; @@ -44,7 +44,7 @@ .error(function () { $scope.form.supportsDimensions = false; $scope.form.preview = ""; - $scope.form.info = "Computer says no"; + $scope.form.info = "Could not embed media - please ensure the URL is valid"; }); } else { $scope.form.supportsDimensions = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html index 83be1a7782..2b2d3bd1cf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.html @@ -46,18 +46,18 @@
-
+ + + -
+ + Link your {{login.caption}} account + + - -
+ + +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html index adb57b4af0..c0e22c4d27 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html @@ -31,8 +31,8 @@
- diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.controller.js index 6fae3cd1d2..0ae93baf27 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/embed/embed.controller.js @@ -53,7 +53,7 @@ break; case 1: //error - $scope.model.embed.info = "Computer says no"; + $scope.model.embed.info = "Could not embed media - please ensure the URL is valid"; break; case 2: $scope.model.embed.preview = data.Markup; @@ -65,7 +65,7 @@ .error(function() { $scope.model.embed.supportsDimensions = false; $scope.model.embed.preview = ""; - $scope.model.embed.info = "Computer says no"; + $scope.model.embed.info = "Could not embed media - please ensure the URL is valid"; }); } else { $scope.model.embed.supportsDimensions = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html index 9a04e9117e..666adabb01 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html @@ -47,18 +47,18 @@
-
+ + + -
+ + Link your {{login.caption}} account + + - + +
@@ -167,7 +167,7 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js index 4a19ea9926..5f6bb23001 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/examinemgmt.controller.js @@ -1,4 +1,4 @@ -function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeout) { +function ExamineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeout) { $scope.indexerDetails = []; $scope.searcherDetails = []; @@ -6,7 +6,9 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo function checkProcessing(indexer, checkActionName) { umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", checkActionName, { indexerName: indexer.name })), + $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", + checkActionName, + { indexerName: indexer.name })), 'Failed to check index processing') .then(function(data) { @@ -17,70 +19,73 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo indexer[k] = data[k]; } indexer.isProcessing = false; - } - else { - $timeout(function () { - //don't continue if we've tried 100 times - if (indexer.processingAttempts < 100) { - checkProcessing(indexer, checkActionName); - //add an attempt - indexer.processingAttempts++; - } - else { - //we've exceeded 100 attempts, stop processing - indexer.isProcessing = false; - } - }, 1000); + } else { + $timeout(function() { + //don't continue if we've tried 100 times + if (indexer.processingAttempts < 100) { + checkProcessing(indexer, checkActionName); + //add an attempt + indexer.processingAttempts++; + } else { + //we've exceeded 100 attempts, stop processing + indexer.isProcessing = false; + } + }, + 1000); } }); } - $scope.search = function (searcher, e) { + $scope.search = function(searcher, e) { if (e && e.keyCode !== 13) { return; } umbRequestHelper.resourcePromise( - $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearchResults", { - searcherName: searcher.name, - query: encodeURIComponent(searcher.searchText), - queryType: searcher.searchType - })), + $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", + "GetSearchResults", + { + searcherName: searcher.name, + query: encodeURIComponent(searcher.searchText), + queryType: searcher.searchType + })), 'Failed to search') .then(function(searchResults) { searcher.isSearching = true; searcher.searchResults = searchResults; }); } - + $scope.toggle = function(provider, propName) { if (provider[propName] !== undefined) { provider[propName] = !provider[propName]; - } - else { + } else { provider[propName] = true; } } $scope.rebuildIndex = function(indexer) { if (confirm("This will cause the index to be rebuilt. " + - "Depending on how much content there is in your site this could take a while. " + - "It is not recommended to rebuild an index during times of high website traffic " + - "or when editors are editing content.")) { + "Depending on how much content there is in your site this could take a while. " + + "It is not recommended to rebuild an index during times of high website traffic " + + "or when editors are editing content.")) { indexer.isProcessing = true; indexer.processingAttempts = 0; umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "PostRebuildIndex", { indexerName: indexer.name })), + $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", + "PostRebuildIndex", + { indexerName: indexer.name })), 'Failed to rebuild index') - .then(function () { + .then(function() { //rebuilding has started, nothing is returned accept a 200 status code. //lets poll to see if it is done. - $timeout(function () { - checkProcessing(indexer, "PostCheckRebuildIndex"); - }, 1000); + $timeout(function() { + checkProcessing(indexer, "PostCheckRebuildIndex"); + }, + 1000); }); } @@ -88,20 +93,23 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo $scope.optimizeIndex = function(indexer) { if (confirm("This will cause the index to be optimized which will improve its performance. " + - "It is not recommended to optimize an index during times of high website traffic " + - "or when editors are editing content.")) { + "It is not recommended to optimize an index during times of high website traffic " + + "or when editors are editing content.")) { indexer.isProcessing = true; umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "PostOptimizeIndex", { indexerName: indexer.name })), + $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", + "PostOptimizeIndex", + { indexerName: indexer.name })), 'Failed to optimize index') - .then(function () { + .then(function() { //optimizing has started, nothing is returned accept a 200 status code. //lets poll to see if it is done. - $timeout(function () { - checkProcessing(indexer, "PostCheckOptimizeIndex"); - }, 1000); + $timeout(function() { + checkProcessing(indexer, "PostCheckOptimizeIndex"); + }, + 1000); }); } @@ -111,36 +119,34 @@ function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeo searcher.isSearching = true; } - //go get the data //combine two promises and execute when they are both done $q.all([ - //get the indexer details - umbRequestHelper.resourcePromise( - $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetIndexerDetails")), - 'Failed to retrieve indexer details') - .then(function(data) { - $scope.indexerDetails = data; - }), - - //get the searcher details - umbRequestHelper.resourcePromise( - $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearcherDetails")), - 'Failed to retrieve searcher details') - .then(function(data) { - $scope.searcherDetails = data; - for (var s in $scope.searcherDetails) { - $scope.searcherDetails[s].searchType = "text"; - } - }) - - ]).then(function () { - //all init loading is complete - $scope.loading = false; - }); - + //get the indexer details + umbRequestHelper.resourcePromise( + $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetIndexerDetails")), + 'Failed to retrieve indexer details') + .then(function(data) { + $scope.indexerDetails = data; + }), + //get the searcher details + umbRequestHelper.resourcePromise( + $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearcherDetails")), + 'Failed to retrieve searcher details') + .then(function(data) { + $scope.searcherDetails = data; + for (var s in $scope.searcherDetails) { + $scope.searcherDetails[s].searchType = "text"; + } + }) + ]) + .then(function() { + //all init loading is complete + $scope.loading = false; + }); } -angular.module("umbraco").controller("Umbraco.Dashboard.ExamineMgmtController", examineMgmtController); \ No newline at end of file + +angular.module("umbraco").controller("Umbraco.Dashboard.ExamineMgmtController", ExamineMgmtController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js new file mode 100644 index 0000000000..8631b09a45 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.controller.js @@ -0,0 +1,136 @@ +(function() { + "use strict"; + + function HealthCheckController($scope, healthCheckResource) { + var SUCCESS = 0; + var WARNING = 1; + var ERROR = 2; + var INFO = 3; + + var vm = this; + + vm.viewState = "list"; + vm.groups = []; + vm.selectedGroup = {}; + + vm.getStatus = getStatus; + vm.executeAction = executeAction; + vm.checkAllGroups = checkAllGroups; + vm.checkAllInGroup = checkAllInGroup; + vm.openGroup = openGroup; + vm.setViewState = setViewState; + + // Get a (grouped) list of all health checks + healthCheckResource.getAllChecks() + .then(function(response) { + vm.groups = response; + }); + + function setGroupGlobalResultType(group) { + var totalSuccess = 0; + var totalError = 0; + var totalWarning = 0; + var totalInfo = 0; + + // count total number of statusses + angular.forEach(group.checks, + function(check) { + angular.forEach(check.status, + function(status) { + switch (status.resultType) { + case SUCCESS: + totalSuccess = totalSuccess + 1; + break; + case WARNING: + totalWarning = totalWarning + 1; + break; + case ERROR: + totalError = totalError + 1; + break; + case INFO: + totalInfo = totalInfo + 1; + break; + } + }); + }); + + group.totalSuccess = totalSuccess; + group.totalError = totalError; + group.totalWarning = totalWarning; + group.totalInfo = totalInfo; + + } + + // Get the status of an individual check + function getStatus(check) { + check.loading = true; + check.status = null; + healthCheckResource.getStatus(check.id) + .then(function(response) { + check.loading = false; + check.status = response; + }); + } + + function executeAction(check, index, action) { + check.loading = true; + healthCheckResource.executeAction(action) + .then(function(response) { + check.status[index] = response; + check.loading = false; + }); + } + + function checkAllGroups(groups) { + // set number of checks which has been executed + for (var i = 0; i < groups.length; i++) { + var group = groups[i]; + checkAllInGroup(group, group.checks); + } + vm.groups = groups; + } + + function checkAllInGroup(group, checks) { + group.checkCounter = 0; + group.loading = true; + + angular.forEach(checks, + function(check) { + + check.loading = true; + + healthCheckResource.getStatus(check.id) + .then(function(response) { + check.status = response; + group.checkCounter = group.checkCounter + 1; + check.loading = false; + + // when all checks are done, set global group result + if (group.checkCounter === checks.length) { + setGroupGlobalResultType(group); + group.loading = false; + } + }); + }); + } + + function openGroup(group) { + vm.selectedGroup = group; + vm.viewState = "details"; + } + + function setViewState(state) { + vm.viewState = state; + + if (state === 'list') { + + for (var i = 0; i < vm.groups.length; i++) { + var group = vm.groups[i]; + setGroupGlobalResultType(group); + } + } + } + } + + angular.module("umbraco").controller("Umbraco.Dashboard.HealthCheckController", HealthCheckController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html new file mode 100644 index 0000000000..ca796043e6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html @@ -0,0 +1,139 @@ +
+ +
+ +

Health Check

+
+

The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button. + You can add your own health checks, have a look at the documentation for more information about custom health checks.

+
+ +
+ +
+ +
+ +
+
+ +
{{group.name}}
+ +
+ +
+ +
+ +
+ + {{ group.totalSuccess }} +
+ +
+ + {{ group.totalWarning }} +
+ +
+ + {{ group.totalError }} +
+ +
+ + {{ group.totalInfo }} +
+ +
+ +
+
+ +
+ +
+ +
+ + + + ← Back to overview + + + + +
+ +
+
{{ vm.selectedGroup.name }}
+ +
+ +
+ +
+ +
+
{{ check.name }}
+
{{ check.description }}
+
+ +
+ +
+ + + + +
+ +
+ +
+
+
+
+ +
+
+ + +
+
+ +
+ + + +
+ +
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js new file mode 100644 index 0000000000..1b26a5dc76 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.controller.js @@ -0,0 +1,157 @@ +(function() { + "use strict"; + + function RedirectUrlsController($scope, redirectUrlsResource, notificationsService, $q) { + //...todo + //search by url or url part + //search by domain + //display domain in dashboard results? + + //used to cancel any request in progress if another one needs to take it's place + var vm = this; + var canceler = null; + + vm.dashboard = { + searchTerm: "", + loading: false, + urlTrackerDisabled: false + }; + + vm.pagination = { + pageIndex: 0, + pageNumber: 1, + totalPages: 1, + pageSize: 20 + }; + + vm.goToPage = goToPage; + vm.search = search; + vm.removeRedirect = removeRedirect; + vm.disableUrlTracker = disableUrlTracker; + vm.enableUrlTracker = enableUrlTracker; + vm.filter = filter; + vm.checkEnabled = checkEnabled; + + function activate() { + vm.checkEnabled().then(function() { + vm.search(); + }); + } + + function checkEnabled() { + vm.dashboard.loading = true; + return redirectUrlsResource.isEnabled().then(function (response) { + vm.dashboard.urlTrackerDisabled = response !== "true"; + vm.dashboard.loading = false; + }); + } + + function goToPage(pageNumber) { + vm.pagination.pageIndex = pageNumber - 1; + vm.pagination.pageNumber = pageNumber; + vm.search(); + } + + function search() { + + vm.dashboard.loading = true; + + var searchTerm = vm.dashboard.searchTerm; + if (searchTerm === undefined) { + searchTerm = ""; + } + + redirectUrlsResource.searchRedirectUrls(searchTerm, vm.pagination.pageIndex, vm.pagination.pageSize).then(function(response) { + + vm.redirectUrls = response.searchResults; + + // update pagination + vm.pagination.pageIndex = response.currentPage; + vm.pagination.pageNumber = response.currentPage + 1; + vm.pagination.totalPages = response.pageCount; + + vm.dashboard.loading = false; + + }); + } + + function removeRedirect(redirectToDelete) { + var toggleConfirm = confirm('Are you sure you want to remove the redirect from ' + '"' + redirectToDelete.originalUrl + '"' + " to " + '"' + redirectToDelete.destinationUrl + '"' + " ?"); + + if (toggleConfirm) { + redirectUrlsResource.deleteRedirectUrl(redirectToDelete.redirectId).then(function () { + + var index = vm.redirectUrls.indexOf(redirectToDelete); + vm.redirectUrls.splice(index, 1); + notificationsService.success("Redirect Url Removed!", "Redirect Url has been deleted"); + + // check if new redirects needs to be loaded + if(vm.redirectUrls.length === 0 && vm.pagination.totalPages > 1) { + + // if we are not on the first page - get records from the previous + if(vm.pagination.pageIndex > 0 ) { + vm.pagination.pageIndex = vm.pagination.pageIndex - 1; + vm.pagination.pageNumber = vm.pagination.pageNumber - 1; + } + + search(); + } + + }, function(error) { + notificationsService.error("Redirect Url Error!", "Redirect Url was not deleted"); + }); + } + } + + function disableUrlTracker() { + var toggleConfirm = confirm("Are you sure you want to disable the URL tracker?"); + if (toggleConfirm) { + + redirectUrlsResource.toggleUrlTracker(true).then(function() { + activate(); + notificationsService.success("URL Tracker has now been disabled"); + }, function(error) { + notificationsService.warning("Error disabling the URL Tracker, more information can be found in your log file."); + }); + + } + } + + function enableUrlTracker() { + redirectUrlsResource.toggleUrlTracker(false).then(function() { + activate(); + notificationsService.success("URL Tracker has now been enabled"); + }, function(error) { + notificationsService.warning("Error enabling the URL Tracker, more information can be found in your log file."); + }); + } + + var filterDebounced = _.debounce(function(e) { + + $scope.$apply(function() { + + //a canceler exists, so perform the cancelation operation and reset + if (canceler) { + canceler.resolve(); + canceler = $q.defer(); + } else { + canceler = $q.defer(); + } + + vm.search(); + + }); + + }, 200); + + function filter() { + vm.dashboard.loading = true; + filterDebounced(); + } + + activate(); + + } + + angular.module("umbraco").controller("Umbraco.Dashboard.RedirectUrlsController", RedirectUrlsController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html new file mode 100644 index 0000000000..50c350da5f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/redirecturls.html @@ -0,0 +1,119 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
Original Url
+
Redirected To
+
Date Created
+
+
+
+ +
+ +
+ +
+ +
+ + + + + +
+ {{redirectUrl.createDateUtc | date:'medium'}} +
+ +
+ Edit + +
+ +
+ +
+ +
+ + +
No redirects have been made
+ When a published page gets renamed a redirect will automatically be made to the new page +
+ + + + + +
+ + +
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.controller.js index 09b638b05f..bb8696aca0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.controller.js @@ -1,4 +1,4 @@ -function xmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http, $q, $timeout) { +function XmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http) { function check(item) { var action = item.check; @@ -20,12 +20,12 @@ function xmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http, "or when editors are editing content.")) { item.fixing = true; umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("xmlDataIntegrityBaseUrl", action)), - 'Failed to retrieve data integrity status') - .then(function (result) { - item.fixing = false; - item.invalid = result === "false"; - }); + $http.post(umbRequestHelper.getApiUrl("xmlDataIntegrityBaseUrl", action)), + 'Failed to retrieve data integrity status') + .then(function(result) { + item.fixing = false; + item.invalid = result === "false"; + }); } } } @@ -57,6 +57,6 @@ function xmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http, for (var i in $scope.items) { check($scope.items[i]); } - } -angular.module("umbraco").controller("Umbraco.Dashboard.XmlDataIntegrityReportController", xmlDataIntegrityReportController); \ No newline at end of file + +angular.module("umbraco").controller("Umbraco.Dashboard.XmlDataIntegrityReportController", XmlDataIntegrityReportController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html index df235fb254..11f1834ae4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/developer/xmldataintegrityreport.html @@ -26,6 +26,4 @@
- -
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html index fa9849022c..3a45776872 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html @@ -9,6 +9,6 @@
  • Download the Editors Manual for details on working with the Umbraco UI
  • Ask a question in the Community Forum
  • Watch our tutorial videos (some are free, some require a subscription)
  • -
  • Find out about our productivity boosting tools and commercial support
  • +
  • Find out about our productivity boosting tools and commercial support
  • Find out about real-life training and certification opportunities
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/create.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/create.html index 54649d1501..1ebccaa5a3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/create.html @@ -10,7 +10,7 @@ - New Data type + New data type @@ -21,7 +21,7 @@ - Folder + New folder diff --git a/src/Umbraco.Web.UI.Client/src/views/media/emptyrecyclebin.html b/src/Umbraco.Web.UI.Client/src/views/media/emptyrecyclebin.html index 5a05e9ca85..4558a21e2a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/emptyrecyclebin.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/emptyrecyclebin.html @@ -1,8 +1,19 @@
    - - + +
    +
    +
    + +

    + When items are deleted from the recycle bin, they will be gone forever. + Are you sure? +

    + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js index 99f40d14bb..a8be3d0be5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js @@ -10,8 +10,12 @@ function MediaDeleteController($scope, mediaResource, treeService, navigationSer $scope.performDelete = function() { + // stop from firing again on double-click + if ($scope.busy) { return false; } + //mark it for deletion (used in the UI) $scope.currentNode.loading = true; + $scope.busy = true; mediaResource.deleteById($scope.currentNode.id).then(function () { $scope.currentNode.loading = false; @@ -45,6 +49,7 @@ function MediaDeleteController($scope, mediaResource, treeService, navigationSer }, function (err) { $scope.currentNode.loading = false; + $scope.busy = false; //check if response is ysod if (err.status && err.status >= 500) { diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.emptyrecyclebin.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.emptyrecyclebin.controller.js index dd6ceb369e..218654b464 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.emptyrecyclebin.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.emptyrecyclebin.controller.js @@ -6,18 +6,33 @@ * @description * The controller for deleting media */ -function MediaEmptyRecycleBinController($scope, mediaResource, treeService, navigationService) { +function MediaEmptyRecycleBinController($scope, mediaResource, treeService, navigationService, notificationsService, $route) { + + $scope.busy = false; $scope.performDelete = function() { //(used in the UI) + $scope.busy = true; $scope.currentNode.loading = true; - mediaResource.emptyRecycleBin($scope.currentNode.id).then(function () { + mediaResource.emptyRecycleBin($scope.currentNode.id).then(function (result) { + + $scope.busy = false; $scope.currentNode.loading = false; - //TODO: Need to sync tree, etc... + + //show any notifications + if (angular.isArray(result.notifications)) { + for (var i = 0; i < result.notifications.length; i++) { + notificationsService.showNotification(result.notifications[i]); + } + } + treeService.removeChildNodes($scope.currentNode); navigationService.hideMenu(); + + //reload the current view + $route.reload(); }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/delete.controller.js new file mode 100644 index 0000000000..134336afce --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/delete.controller.js @@ -0,0 +1,32 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.Packages.DeleteController + * @function + * + * @description + * The controller for deleting content + */ +function PackageDeleteController($scope, packageResource, treeService, navigationService) { + + $scope.performDelete = function() { + + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + packageResource.deleteCreatedPackage($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + + }; + + $scope.cancel = function() { + navigationService.hideDialog(); + }; +} + +angular.module("umbraco").controller("Umbraco.Editors.Packages.DeleteController", PackageDeleteController); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/delete.html b/src/Umbraco.Web.UI.Client/src/views/packager/delete.html new file mode 100644 index 0000000000..1a802ab657 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/delete.html @@ -0,0 +1,13 @@ +
    +
    + +

    + Are you sure you want to delete {{currentNode.name}} ? +

    + + + + + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/overview.controller.js new file mode 100644 index 0000000000..369d919b7d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/overview.controller.js @@ -0,0 +1,56 @@ +(function () { + "use strict"; + + function PackagesOverviewController($scope, $route, $location, navigationService, $timeout, localStorageService) { + + //Hack! + // if there is a cookie value for packageInstallUri then we need to redirect there, + // the issue is that we still have webforms and we cannot go to a hash location and then window.reload + // because it will double load it. + // we will refresh and then navigate there. + + var installPackageUri = localStorageService.get("packageInstallUri"); + if (installPackageUri) { + localStorageService.remove("packageInstallUri"); + } + if (installPackageUri && installPackageUri !== "installed") { + //navigate to the custom installer screen, if it is just "installed", then we'll + //show the installed view + $location.path(installPackageUri).search(""); + } + else { + var vm = this; + + vm.page = {}; + vm.page.name = "Packages"; + vm.page.navigation = [ + { + "name": "Packages", + "icon": "icon-cloud", + "view": "views/packager/views/repo.html", + "active": !installPackageUri || installPackageUri === "navigation" + }, + { + "name": "Installed", + "icon": "icon-box", + "view": "views/packager/views/installed.html", + "active": installPackageUri === "installed" + }, + { + "name": "Install local", + "icon": "icon-add", + "view": "views/packager/views/install-local.html", + "active": installPackageUri === "local" + } + ]; + + $timeout(function () { + navigationService.syncTree({ tree: "packager", path: "-1" }); + }); + } + + } + + angular.module("umbraco").controller("Umbraco.Editors.Packages.OverviewController", PackagesOverviewController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/overview.html b/src/Umbraco.Web.UI.Client/src/views/packager/overview.html new file mode 100644 index 0000000000..b9857ac53a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/overview.html @@ -0,0 +1,28 @@ +
    + +
    + + + + + + + + + + + + + + + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js new file mode 100644 index 0000000000..e29df0b9ae --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js @@ -0,0 +1,154 @@ +(function () { + "use strict"; + + function PackagesInstallLocalController($scope, $route, $location, Upload, umbRequestHelper, packageResource, localStorageService, $timeout, $window, localizationService) { + + var vm = this; + vm.state = "upload"; + + vm.localPackage = {}; + vm.installPackage = installPackage; + vm.installState = { + status: "", + progress:0 + }; + vm.zipFile = { + uploadStatus: "idle", + uploadProgress: 0, + serverErrorMessage: null + }; + + $scope.handleFiles = function (files, event) { + if (files) { + for (var i = 0; i < files.length; i++) { + upload(files[i]); + } + } + }; + + function upload(file) { + + Upload.upload({ + url: umbRequestHelper.getApiUrl("packageInstallApiBaseUrl", "UploadLocalPackage"), + fields: {}, + file: file + }).progress(function (evt) { + + // calculate progress in percentage + var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); + + // set percentage property on file + vm.zipFile.uploadProgress = progressPercentage; + + // set uploading status on file + vm.zipFile.uploadStatus = "uploading"; + + }).success(function (data, status, headers, config) { + + if (data.notifications && data.notifications.length > 0) { + + // set error status on file + vm.zipFile.uploadStatus = "error"; + + // Throw message back to user with the cause of the error + vm.zipFile.serverErrorMessage = data.notifications[0].message; + + } else { + + // set done status on file + vm.zipFile.uploadStatus = "done"; + loadPackage(); + vm.localPackage = data; + } + + }).error(function (evt, status, headers, config) { + + // set status done + vm.zipFile.uploadStatus = "error"; + + // If file not found, server will return a 404 and display this message + if (status === 404) { + vm.zipFile.serverErrorMessage = "File not found"; + } + else if (status == 400) { + //it's a validation error + vm.zipFile.serverErrorMessage = evt.message; + } + else { + //it's an unhandled error + //if the service returns a detailed error + if (evt.InnerException) { + vm.zipFile.serverErrorMessage = evt.InnerException.ExceptionMessage; + + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { + vm.zipFile.serverErrorMessage = "File too large to upload"; + } + + } else if (evt.Message) { + file.serverErrorMessage = evt.Message; + } + } + }); + } + + function loadPackage() { + if (vm.zipFile.uploadStatus === "done") { + vm.state = "packageDetails"; + } + } + + function installPackage() { + vm.installState.status = localizationService.localize("packager_installStateImporting"); + vm.installState.progress = "0"; + + packageResource + .import(vm.localPackage) + .then(function(pack) { + vm.installState.progress = "25"; + vm.installState.status = localizationService.localize("packager_installStateInstalling"); + vm.installState.progress = "50"; + return packageResource.installFiles(pack); + }, + installError) + .then(function(pack) { + vm.installState.status = localizationService.localize("packager_installStateRestarting"); + vm.installState.progress = "75"; + return packageResource.installData(pack); + }, + installError) + .then(function(pack) { + vm.installState.status = localizationService.localize("packager_installStateComplete"); + vm.installState.progress = "100"; + return packageResource.cleanUp(pack); + }, + installError) + .then(function(result) { + + if (result.postInstallationPath) { + //Put the redirect Uri in a cookie so we can use after reloading + localStorageService.set("packageInstallUri", result.postInstallationPath); + } + else { + //set to a constant value so it knows to just go to the installed view + localStorageService.set("packageInstallUri", "installed"); + } + + //reload on next digest (after cookie) + $timeout(function () { + $window.location.reload(true); + }); + + }, + installError); + } + + function installError() { + //This will return a rejection meaning that the promise change above will stop + return $q.reject(); + } + } + + angular.module("umbraco").controller("Umbraco.Editors.Packages.InstallLocalController", PackagesInstallLocalController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html new file mode 100644 index 0000000000..07fb21d00b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html @@ -0,0 +1,137 @@ +
    + +
    + + +
    +
    + + +
    + + +
    + + + + Drop to upload + + +
    + - or click here to choose files +
    + +
    + {{vm.zipFile.serverErrorMessage}} +
    +
    +
    + + +

    Upload package

    +

    + Install a local package by selecting it from your machine. Only install packages from sources you know and trust. +

    + +
    +
    +
    + +
    + + + + ← Cancel and upload another package + + + +
    + + +
    +
    + +
    +
    + + +
    + + +
    +

    {{ vm.localPackage.name }}

    + + + +
    + Version + {{ vm.localPackage.version }} +
    + + + +
    + Read me +
    + +
    + +
    + + +
    + +
    + + +
    + +
    + This package cannot be installed, it requires a minimum Umbraco version of {{vm.localPackage.umbracoVersion}} +
    +
    +

    {{vm.installState.status}}

    +
    + +
    +
    + +
    +
    +
    +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.controller.js new file mode 100644 index 0000000000..ce1d2cca0f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.controller.js @@ -0,0 +1,62 @@ +(function () { + "use strict"; + + function PackagesInstalledController($scope, $route, $location, packageResource, $timeout, $window, localStorageService, localizationService) { + + var vm = this; + + vm.confirmUninstall = confirmUninstall; + vm.uninstallPackage = uninstallPackage; + vm.state = "list"; + vm.installState = { + status: "" + }; + vm.package = {}; + + function init() { + packageResource.getInstalled() + .then(function (packs) { + vm.installedPackages = packs; + }); + vm.installState.status = ""; + vm.state = "list"; + } + + function confirmUninstall(pck) { + vm.state = "packageDetails"; + vm.package = pck; + } + + function uninstallPackage(installedPackage) { + vm.installState.status = localizationService.localize("packager_installStateUninstalling"); + vm.installState.progress = "0"; + + packageResource.uninstall(installedPackage.id) + .then(function () { + + if (installedPackage.files.length > 0) { + vm.installState.status = localizationService.localize("packager_installStateComplete"); + vm.installState.progress = "100"; + + //set this flag so that on refresh it shows the installed packages list + localStorageService.set("packageInstallUri", "installed"); + + //reload on next digest (after cookie) + $timeout(function () { + $window.location.reload(true); + }); + + } + else { + init(); + } + }); + } + + init(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.Packages.InstalledController", PackagesInstalledController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html new file mode 100644 index 0000000000..5da3c33a76 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html @@ -0,0 +1,122 @@ +
    + + +
    + +
    + +
    Installed packages
    + +
    + +
    + +
    + + +
    + +
    +
    {{ installedPackage.name }}
    +
    + {{ installedPackage.version }} | {{ installedPackage.url }}| {{ installedPackage.author }} +
    +
    + +
    + +
    + +
    + +
    + +
    + + +

    You don’t have any packages installed.

    +

    You don’t have any packages installed. Either install a local package by selecting it from your machine, or browse through available packages using the "Package" icon in the top right of your screen."

    +
    + +
    + + +
    + + + + ← Take me back + + + +
    + +
    + +
    +
    + + +
    + + +
    +

    {{ vm.package.name }}

    + + + +
    + Version + {{ vm.package.version }} +
    + +
    + License + {{ vm.package.license }} +
    + +
    + Read me +
    + {{ vm.package.readme }} +
    + +
    + + +
    + +
    + + +
    + +
    +

    {{vm.installState.status}}

    +
    + +
    +
    + +
    + +
    +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js new file mode 100644 index 0000000000..e4afb661e3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js @@ -0,0 +1,286 @@ +(function () { + "use strict"; + + function PackagesRepoController($scope, $route, $location, $timeout, ourPackageRepositoryResource, $q, packageResource, localStorageService, localizationService) { + + var vm = this; + + vm.packageViewState = "packageList"; + vm.categories = []; + vm.loading = true; + vm.pagination = { + pageNumber: 1, + totalPages: 10, + pageSize: 24 + }; + vm.searchQuery = ""; + vm.installState = { + status: "", + progress: 0, + type: "ok" + }; + vm.selectCategory = selectCategory; + vm.showPackageDetails = showPackageDetails; + vm.setPackageViewState = setPackageViewState; + vm.nextPage = nextPage; + vm.prevPage = prevPage; + vm.goToPage = goToPage; + vm.installPackage = installPackage; + vm.downloadPackage = downloadPackage; + vm.openLightbox = openLightbox; + vm.closeLightbox = closeLightbox; + vm.search = search; + + var currSort = "Latest"; + //used to cancel any request in progress if another one needs to take it's place + var canceler = null; + + function getActiveCategory() { + if (vm.searchQuery !== "") { + return ""; + } + for (var i = 0; i < vm.categories.length; i++) { + if (vm.categories[i].active === true) { + return vm.categories[i].name; + } + } + return ""; + } + + function init() { + + vm.loading = true; + + $q.all([ + ourPackageRepositoryResource.getCategories() + .then(function(cats) { + vm.categories = cats; + }), + ourPackageRepositoryResource.getPopular(8) + .then(function(pack) { + vm.popular = pack.packages; + }), + ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort) + .then(function(pack) { + vm.packages = pack.packages; + vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); + }) + ]) + .then(function() { + vm.loading = false; + }); + + } + + function selectCategory(selectedCategory, categories) { + var reset = false; + for (var i = 0; i < categories.length; i++) { + var category = categories[i]; + if (category.name === selectedCategory.name && category.active === true) { + //it's already selected, let's unselect to show all again + reset = true; + } + category.active = false; + } + + vm.loading = true; + vm.searchQuery = ""; + var searchCategory = selectedCategory.name; + if (reset === true) { + searchCategory = ""; + } + + currSort = "Latest"; + + $q.all([ + ourPackageRepositoryResource.getPopular(8, searchCategory) + .then(function(pack) { + vm.popular = pack.packages; + }), + ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort, searchCategory, vm.searchQuery) + .then(function(pack) { + vm.packages = pack.packages; + vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); + vm.pagination.pageNumber = 1; + }) + ]) + .then(function() { + vm.loading = false; + selectedCategory.active = reset === false; + }); + } + + function showPackageDetails(selectedPackage) { + ourPackageRepositoryResource.getDetails(selectedPackage.id) + .then(function (pack) { + packageResource.validateInstalled(pack.name, pack.latestVersion) + .then(function() { + //ok, can install + vm.package = pack; + vm.package.isValid = true; + vm.packageViewState = "packageDetails"; + }, function() { + //nope, cannot install + vm.package = pack; + vm.package.isValid = false; + vm.packageViewState = "packageDetails"; + }) + }); + } + + function setPackageViewState(state) { + if(state) { + vm.packageViewState = state; + } + } + + function nextPage(pageNumber) { + ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery) + .then(function (pack) { + vm.packages = pack.packages; + vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); + }); + } + + function prevPage(pageNumber) { + ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery) + .then(function (pack) { + vm.packages = pack.packages; + vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); + }); + } + + function goToPage(pageNumber) { + ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery) + .then(function (pack) { + vm.packages = pack.packages; + vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); + }); + } + + function downloadPackage(selectedPackage) { + vm.loading = true; + + packageResource + .fetch(selectedPackage.id) + .then(function(pack) { + vm.packageViewState = "packageInstall"; + vm.loading = false; + vm.localPackage = pack; + vm.localPackage.allowed = true; + }, function (evt, status, headers, config) { + + if (status == 400) { + //it's a validation error + vm.installState.type = "error"; + vm.zipFile.serverErrorMessage = evt.message; + } + }); + } + + function error(e, args) { + //This will return a rejection meaning that the promise change above will stop + return $q.reject(); + } + + function installPackage(selectedPackage) { + + vm.installState.status = localizationService.localize("packager_installStateImporting"); + vm.installState.progress = "0"; + + packageResource + .import(selectedPackage) + .then(function(pack) { + vm.installState.status = localizationService.localize("packager_installStateInstalling"); + vm.installState.progress = "33"; + return packageResource.installFiles(pack); + }, + error) + .then(function(pack) { + vm.installState.status = localizationService.localize("packager_installStateRestarting"); + vm.installState.progress = "66"; + return packageResource.installData(pack); + }, + error) + .then(function(pack) { + vm.installState.status = localizationService.localize("packager_installStateComplete"); + vm.installState.progress = "100"; + return packageResource.cleanUp(pack); + }, + error) + .then(function(result) { + + if (result.postInstallationPath) { + //Put the redirect Uri in a cookie so we can use after reloading + localStorageService.set("packageInstallUri", result.postInstallationPath); + } + + //reload on next digest (after cookie) + $timeout(function() { + window.location.reload(true); + }); + + }, + error); + } + + function openLightbox(itemIndex, items) { + vm.lightbox = { + show: true, + items: items, + activeIndex: itemIndex + }; + } + + function closeLightbox() { + vm.lightbox.show = false; + vm.lightbox = null; + } + + + var searchDebounced = _.debounce(function(e) { + + $scope.$apply(function () { + + //a canceler exists, so perform the cancelation operation and reset + if (canceler) { + canceler.resolve(); + canceler = $q.defer(); + } + else { + canceler = $q.defer(); + } + + currSort = vm.searchQuery ? "Default" : "Latest"; + + ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, + vm.pagination.pageSize, + currSort, + "", + vm.searchQuery, + canceler) + .then(function(pack) { + vm.packages = pack.packages; + vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); + vm.pagination.pageNumber = 1; + vm.loading = false; + //set back to null so it can be re-created + canceler = null; + }); + + }); + + }, 200); + + function search(searchQuery) { + vm.loading = true; + searchDebounced(); + } + + init(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.Packages.RepoController", PackagesRepoController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html new file mode 100644 index 0000000000..b19eb63232 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html @@ -0,0 +1,351 @@ +
    + + + + +
    + +
    + +
    + +
    + + + + + + + +
    + + + + +
    + + + +

    We couldn't find anything for '{{ vm.searchQuery }}'

    +

    Please try searching for another package or browse through the categories.

    +
    + +
    + +
    + + + +
    + + + + ← Take me back + + + +
    + +
    + +
    + +
    {{ vm.package.name }}
    + +
    + + + + + + +
    + +
    + +
    + + + +
    + +
    +
    + +
    + + +
    + +
    +
    {{ vm.package.ownerInfo.owner }}
    +
    + {{ vm.package.ownerInfo.owner }} has {{ vm.package.ownerInfo.karma }} karma points +
    +
    +
    +
    + +
    +
    Information
    +
    + +
    +
    Owner:
    +
    {{vm.package.ownerInfo.owner}}
    +
    + +
    +
    Contributors:
    +
    + {{ contributor }} +
    +
    + +
    +
    Created:
    +
    {{vm.package.created}}
    +
    + +
    +
    Current version:
    +
    {{vm.package.latestVersion}}
    +
    + +
    +
    .Net Version:
    +
    {{vm.package.information.netVersion}}
    +
    + +
    +
    License:
    +
    {{vm.package.licenseName}}
    +
    + +
    +
    Downloads:
    +
    {{vm.package.downloads}}
    +
    + +
    +
    Likes:
    +
    {{vm.package.likes}}
    +
    + +
    +
    + +
    +
    Compatibility
    +
    This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be gauranteed for versions reported below 100%
    +
    +
    + {{compatibility.version}} + ({{compatibility.percentage}}%) +
    + + + + +
    +
    + +
    +
    External sources
    + + +
    + +
    + +
    +
    +
    + + +
    + + + ← Take me back + + + +
    + +
    + +
    + +
    +
    + + +
    + + +
    +

    {{ vm.localPackage.name }}

    + + + +
    + Version + {{ vm.localPackage.version }} +
    + + + +
    + Read me +
    + +
    + +
    + + +
    + +
    + + +
    + +
    + This package cannot be installed, it requires a minimum Umbraco version of {{vm.localPackage.umbracoVersion}} +
    + +
    +

    {{vm.installState.status}}

    +
    + +
    +
    + +
    +
    +
    +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/valuetype.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/valuetype.html new file mode 100644 index 0000000000..59b7c55c19 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/valuetype.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js index 693bd49ec6..e777b0a409 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js @@ -1,4 +1,4 @@ -function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element) { +function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element, dateHelper) { //setup the default config var config = { @@ -22,6 +22,8 @@ function dateTimePickerController($scope, notificationsService, assetsService, a $scope.model.config.format = $scope.model.config.pickTime ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD"; } + + $scope.hasDatetimePickerValue = $scope.model.value ? true : false; $scope.datetimePickerValue = null; @@ -43,20 +45,46 @@ function dateTimePickerController($scope, notificationsService, assetsService, a if (e.date && e.date.isValid()) { $scope.datePickerForm.datepicker.$setValidity("pickerError", true); $scope.hasDatetimePickerValue = true; - $scope.datetimePickerValue = e.date.format($scope.model.config.format); - $scope.model.value = $scope.datetimePickerValue; + $scope.datetimePickerValue = e.date.format($scope.model.config.format); } else { $scope.hasDatetimePickerValue = false; $scope.datetimePickerValue = null; } - + + setModelValue(); + if (!$scope.model.config.pickTime) { $element.find("div:first").datetimepicker("hide", 0); } }); } + //sets the scope model value accordingly - this is the value to be sent up to the server and depends on + // if the picker is configured to offset time. We always format the date/time in a specific format for sending + // to the server, this is different from the format used to display the date/time. + function setModelValue() { + if ($scope.hasDatetimePickerValue) { + var elementData = $element.find("div:first").data().DateTimePicker; + if ($scope.model.config.pickTime) { + //check if we are supposed to offset the time + if ($scope.model.value && $scope.model.config.offsetTime === "1" && Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) { + $scope.model.value = dateHelper.convertToServerStringTime(elementData.getDate(), Umbraco.Sys.ServerVariables.application.serverTimeOffset); + $scope.serverTime = dateHelper.convertToServerStringTime(elementData.getDate(), Umbraco.Sys.ServerVariables.application.serverTimeOffset, "YYYY-MM-DD HH:mm:ss Z"); + } + else { + $scope.model.value = elementData.getDate().format("YYYY-MM-DD HH:mm:ss"); + } + } + else { + $scope.model.value = elementData.getDate().format("YYYY-MM-DD"); + } + } + else { + $scope.model.value = null; + } + } + var picker = null; $scope.clearDate = function() { @@ -66,6 +94,21 @@ function dateTimePickerController($scope, notificationsService, assetsService, a $scope.datePickerForm.datepicker.$setValidity("pickerError", true); } + $scope.serverTime = null; + $scope.serverTimeNeedsOffsetting = false; + if (Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) { + // Will return something like 120 + var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset; + + // Will return something like -120 + var localOffset = new Date().getTimezoneOffset(); + + // If these aren't equal then offsetting is needed + // note the minus in front of serverOffset needed + // because C# and javascript return the inverse offset + $scope.serverTimeNeedsOffsetting = (-serverOffset !== localOffset); + } + //get the current user to see if we can localize this picker userService.getCurrentUser().then(function (user) { @@ -97,8 +140,17 @@ function dateTimePickerController($scope, notificationsService, assetsService, a }); if ($scope.hasDatetimePickerValue) { - //assign value to plugin/picker - var dateVal = $scope.model.value ? moment($scope.model.value, "YYYY-MM-DD HH:mm:ss") : moment(); + var dateVal; + //check if we are supposed to offset the time + if ($scope.model.value && $scope.model.config.offsetTime === "1" && $scope.serverTimeNeedsOffsetting) { + //get the local time offset from the server + dateVal = dateHelper.convertToLocalMomentTime($scope.model.value, Umbraco.Sys.ServerVariables.application.serverTimeOffset); + $scope.serverTime = dateHelper.convertToServerStringTime(dateVal, Umbraco.Sys.ServerVariables.application.serverTimeOffset, "YYYY-MM-DD HH:mm:ss Z"); + } + else { + //create a normal moment , no offset required + var dateVal = $scope.model.value ? moment($scope.model.value, "YYYY-MM-DD HH:mm:ss") : moment(); + } element.datetimepicker("setValue", dateVal); $scope.datetimePickerValue = dateVal.format($scope.model.config.format); @@ -117,18 +169,7 @@ function dateTimePickerController($scope, notificationsService, assetsService, a var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - if ($scope.hasDatetimePickerValue) { - var elementData = $element.find("div:first").data().DateTimePicker; - if ($scope.model.config.pickTime) { - $scope.model.value = elementData.getDate().format("YYYY-MM-DD HH:mm:ss"); - } - else { - $scope.model.value = elementData.getDate().format("YYYY-MM-DD"); - } - } - else { - $scope.model.value = null; - } + setModelValue(); }); //unbind doc click event! $scope.$on('$destroy', function () { @@ -142,17 +183,7 @@ function dateTimePickerController($scope, notificationsService, assetsService, a }); var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - if ($scope.hasDatetimePickerValue) { - if ($scope.model.config.pickTime) { - $scope.model.value = $element.find("div:first").data().DateTimePicker.getDate().format("YYYY-MM-DD HH:mm:ss"); - } - else { - $scope.model.value = $element.find("div:first").data().DateTimePicker.getDate().format("YYYY-MM-DD"); - } - } - else { - $scope.model.value = null; - } + setModelValue(); }); //unbind doc click event! diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html index e6d49e5c6c..003e2ada60 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html @@ -18,6 +18,10 @@ {{datePickerForm.datepicker.errorMsg}} Invalid date +

    + This translates to the following time on the server: {{serverTime}}
    + What does this mean? +

    Clear date

    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index 4285cf0f77..df21541f09 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -71,7 +71,10 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag "GetBigThumbnail", [{ originalImagePath: file.file }]); + var extension = file.file.substring(file.file.lastIndexOf(".") + 1, file.file.length); + file.thumbnail = thumbnailUrl; + file.extension = extension.toLowerCase(); }); $scope.clearFiles = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html index c213378b23..0905de07f9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html @@ -1,10 +1,15 @@ 
    - +
    • - - {{file.file}} + + {{file.file}} + {{file.file}} + + + .{{file.extension}} + {{file.file}}
    • diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html index b1343a960b..093afd3c73 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html @@ -75,6 +75,7 @@ checklist-model="currentCell.allowed" checklist-value="editor.alias"> {{editor.name}} + ({{editor.alias}})
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/macro.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/macro.html index 732b551b52..f36a870423 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/macro.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/macro.html @@ -7,7 +7,7 @@
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html index 3061f01c29..9d9626694a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html @@ -1,5 +1,5 @@
    -
    +

    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html index a2c0ff534e..4360996b55 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html @@ -11,6 +11,13 @@ on-click-name="vm.goToItem"> + +
    No content has been added
    +
    No members have been added
    +
    + @@ -18,7 +25,7 @@ @@ -68,7 +75,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html index 96bebb7abf..29a9875df8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html @@ -49,6 +49,13 @@ on-sort="vm.sort"> + +
    No content has been added
    +
    No members have been added
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 518c27d110..0a1a14fc44 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -1,4 +1,4 @@ -function listViewController($rootScope, $scope, $routeParams, $injector, $cookieStore, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState, $timeout, $q, mediaResource, listViewHelper, userService) { +function listViewController($rootScope, $scope, $routeParams, $injector, $cookieStore, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState, $timeout, $q, mediaResource, listViewHelper, userService, navigationService, treeService) { //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content // that isn't created yet, if we continue this will use the parent id in the route params which isn't what @@ -151,7 +151,6 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie layouts: $scope.model.config.layouts, activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts) }, - orderBySystemField: true, allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish, allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish, allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy, @@ -159,6 +158,15 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete }; + // Check if selected order by field is actually custom field + for (var j = 0; j < $scope.options.includeProperties.length; j++) { + var includedProperty = $scope.options.includeProperties[j]; + if (includedProperty.alias.toLowerCase() === $scope.options.orderBy.toLowerCase()) { + $scope.options.orderBySystemField = includedProperty.isSystem === 1; + break; + } + } + //update all of the system includeProperties to enable sorting _.each($scope.options.includeProperties, function (e, i) { @@ -169,62 +177,60 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie e.allowSorting = true; } - // Another special case for lasted edited data/update date for media, again this field isn't available on the base table so we can't sort by it - if (e.isSystem && $scope.entityType == "media") { - e.allowSorting = e.alias != 'updateDate'; - } - // Another special case for members, only fields on the base table (cmsMember) can be used for sorting if (e.isSystem && $scope.entityType == "member") { e.allowSorting = e.alias == 'username' || e.alias == 'email'; } if (e.isSystem) { - //localize the header - var key = getLocalizedKey(e.alias); - localizationService.localize(key).then(function (v) { - e.header = v; - }); - } + //localize the header + var key = getLocalizedKey(e.alias); + localizationService.localize(key).then(function (v) { + e.header = v; + }); + } }); $scope.selectLayout = function (selectedLayout) { $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts); }; - function showNotificationsAndReset(err, reload, successMsg) { + function showNotificationsAndReset(err, reload, successMsg) { - //check if response is ysod - if (err.status && err.status >= 500) { + //check if response is ysod + if (err.status && err.status >= 500) { - // Open ysod overlay - $scope.ysodOverlay = { - view: "ysod", - error: err, - show: true - }; - } + // Open ysod overlay + $scope.ysodOverlay = { + view: "ysod", + error: err, + show: true + }; + } - $timeout(function () { - $scope.bulkStatus = ""; - $scope.actionInProgress = false; - }, 500); + $timeout(function() { + $scope.bulkStatus = ""; + $scope.actionInProgress = false; + }, + 500); - if (reload === true) { - $scope.reloadView($scope.contentId); - } + if (reload === true) { + $scope.reloadView($scope.contentId); + } - if (err.data && angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } - } - else if (successMsg) { - notificationsService.success("Done", successMsg); - } - } + if (err.data && angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } else if (successMsg) { + localizationService.localize("bulk_done") + .then(function(v) { + notificationsService.success(v, successMsg); + }); + } + } - $scope.next = function (pageNumber) { + $scope.next = function (pageNumber) { $scope.options.pageNumber = pageNumber; $scope.reloadView($scope.contentId); }; @@ -357,7 +363,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie $scope.actionInProgress = true; $scope.bulkStatus = getStatusMsg(0, selected.length); - serial(selected, fn, getStatusMsg, 0).then(function (result) { + return serial(selected, fn, getStatusMsg, 0).then(function (result) { // executes once the whole selection has been processed // in case of an error (caught by serial), result will be the error if (!(result.data && angular.isArray(result.data.notifications))) @@ -365,64 +371,137 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie }); } - $scope.delete = function () { - applySelected( - function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, - function (count, total) { return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Deleted " + total + " item" + (total > 1 ? "s" : ""); }, - "Sure you want to delete?"); - }; + $scope.delete = function() { + var confirmDeleteText = ""; + + localizationService.localize("defaultdialogs_confirmdelete") + .then(function(value) { + confirmDeleteText = value; + + var attempt = + applySelected( + function(selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, + function(count, total) { + var key = (total === 1 ? "bulk_deletedItemOfItem" : "bulk_deletedItemOfItems"); + return localizationService.localize(key, [count, total]); + }, + function(total) { + var key = (total === 1 ? "bulk_deletedItem" : "bulk_deletedItems"); + return localizationService.localize(key, [total]); + }, + confirmDeleteText + "?"); + if (attempt) { + attempt.then(function() { + //executes if all is successful, let's sync the tree + var activeNode = appState.getTreeState("selectedNode"); + if (activeNode) { + navigationService.reloadNode(activeNode); + } + }); + } + }); + }; $scope.publish = function () { - applySelected( - function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, - function (count, total) { return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Published " + total + " item" + (total > 1 ? "s" : ""); }); + applySelected( + function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, + function (count, total) { + var key = (total === 1 ? "bulk_publishedItemOfItem" : "bulk_publishedItemOfItems"); + return localizationService.localize(key, [count, total]); + }, + function (total) { + var key = (total === 1 ? "bulk_publishedItem" : "bulk_publishedItems"); + return localizationService.localize(key, [total]); + }); }; - $scope.unpublish = function () { - applySelected( - function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, - function (count, total) { return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Unpublished " + total + " item" + (total > 1 ? "s" : ""); }); - }; + $scope.unpublish = function() { + applySelected( + function(selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, + function(count, total) { + var key = (total === 1 ? "bulk_unpublishedItemOfItem" : "bulk_unpublishedItemOfItems"); + return localizationService.localize(key, [count, total]); + }, + function(total) { + var key = (total === 1 ? "bulk_unpublishedItem" : "bulk_unpublishedItems"); + return localizationService.localize(key, [total]); + }); + }; - $scope.move = function () { - $scope.moveDialog = {}; - $scope.moveDialog.title = "Move"; - $scope.moveDialog.section = $scope.entityType; - $scope.moveDialog.currentNode = $scope.contentId; - $scope.moveDialog.view = "move"; - $scope.moveDialog.show = true; + $scope.move = function() { + $scope.moveDialog = {}; + $scope.moveDialog.title = localizationService.localize("general_move"); + $scope.moveDialog.section = $scope.entityType; + $scope.moveDialog.currentNode = $scope.contentId; + $scope.moveDialog.view = "move"; + $scope.moveDialog.show = true; - $scope.moveDialog.submit = function (model) { + $scope.moveDialog.submit = function(model) { - if (model.target) { - performMove(model.target); - } + if (model.target) { + performMove(model.target); + } - $scope.moveDialog.show = false; - $scope.moveDialog = null; - }; + $scope.moveDialog.show = false; + $scope.moveDialog = null; + }; - $scope.moveDialog.close = function (oldModel) { - $scope.moveDialog.show = false; - $scope.moveDialog = null; - }; + $scope.moveDialog.close = function(oldModel) { + $scope.moveDialog.show = false; + $scope.moveDialog = null; + }; + + }; - }; function performMove(target) { - applySelected( - function (selected, index) { return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }); }, - function (count, total) { return "Moved " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Moved " + total + " item" + (total > 1 ? "s" : ""); }); + //NOTE: With the way this applySelected/serial works, I'm not sure there's a better way currently to return + // a specific value from one of the methods, so we'll have to try this way. Even though the first method + // will fire once per every node moved, the destination path will be the same and we need to use that to sync. + var newPath = null; + applySelected( + function(selected, index) { + return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }) + .then(function(path) { + newPath = path; + return path; + }); + }, + function(count, total) { + var key = (total === 1 ? "bulk_movedItemOfItem" : "bulk_movedItemOfItems"); + return localizationService.localize(key, [count, total]); + }, + function(total) { + var key = (total === 1 ? "bulk_movedItem" : "bulk_movedItems"); + return localizationService.localize(key, [total]); + }) + .then(function() { + //executes if all is successful, let's sync the tree + if (newPath) { + + //we need to do a double sync here: first refresh the node where the content was moved, + // then refresh the node where the content was moved from + navigationService.syncTree({ + tree: target.nodeType, + path: newPath, + forceReload: true, + activate: false + }) + .then(function(args) { + //get the currently edited node (if any) + var activeNode = appState.getTreeState("selectedNode"); + if (activeNode) { + navigationService.reloadNode(activeNode); + } + }); + } + }); } $scope.copy = function () { $scope.copyDialog = {}; - $scope.copyDialog.title = "Copy"; + $scope.copyDialog.title = localizationService.localize("general_copy"); $scope.copyDialog.section = $scope.entityType; $scope.copyDialog.currentNode = $scope.contentId; $scope.copyDialog.view = "copy"; @@ -447,8 +526,14 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie function performCopy(target, relateToOriginal) { applySelected( function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); }, - function (count, total) { return "Copied " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Copied " + total + " item" + (total > 1 ? "s" : ""); }); + function (count, total) { + var key = (total === 1 ? "bulk_copiedItemOfItem" : "bulk_copiedItemOfItems"); + return localizationService.localize(key, [count, total]); + }, + function (total) { + var key = (total === 1 ? "bulk_copiedItem" : "bulk_copiedItems"); + return localizationService.localize(key, [total]); + }); } function getCustomPropertyValue(alias, properties) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index 904939b75f..3969483734 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -11,7 +11,7 @@ -
    + + - {{ selectedItemsCount() }} of {{ listViewResultSet.items.length }} selected + {{ selectedItemsCount() }} of {{ listViewResultSet.items.length }} selected
    @@ -86,7 +93,7 @@ type="button" button-style="link" label="Publish" - key="actions_publish" + label-key="actions_publish" icon="icon-globe" action="publish()" disabled="actionInProgress"> @@ -97,7 +104,7 @@ type="button" button-style="link" label="Unpublish" - key="actions_unpublish" + label-key="actions_unpublish" icon="icon-block" action="unpublish()" disabled="actionInProgress"> @@ -108,7 +115,7 @@ type="button" button-style="link" label="Copy" - key="actions_copy" + label-key="actions_copy" icon="icon-documents" action="copy()" disabled="actionInProgress"> @@ -119,7 +126,7 @@ type="button" button-style="link" label="Move" - key="actions_move" + label-key="actions_move" icon="icon-enter" action="move()" disabled="actionInProgress"> @@ -130,7 +137,7 @@ type="button" button-style="link" label="Delete" - key="actions_delete" + label-key="actions_delete" icon="icon-trash" action="delete()" disabled="actionInProgress"> @@ -155,14 +162,16 @@ - - +
    + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js index 4498e281dd..333b91f81f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.controller.js @@ -1,40 +1,95 @@ -function sortByPreValsController($rootScope, $scope, localizationService) { +function sortByPreValsController($rootScope, $scope, localizationService, editorState, listViewPrevalueHelper) { + //Get the prevalue from the correct place + function getPrevalues() { + if (editorState.current.preValues) { + return editorState.current.preValues; + } + else { + return listViewPrevalueHelper.getPrevalues(); + } + } - $scope.sortByFields = [ - { value: "SortOrder", key: "general_sort" }, - { value: "Name", key: "general_name" }, - { value: "VersionDate", key: "content_updateDate" }, - { value: "Updater", key: "content_updatedBy" }, - { value: "CreateDate", key: "content_createDate" }, - { value: "Owner", key: "content_createBy" }, - { value: "ContentTypeAlias", key: "content_documentType" }, - { value: "Published", key: "content_isPublished" }, - { value: "Email", key: "general_email" }, - { value: "Username", key: "general_username" } - ]; - - //now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared - _.each($scope.sortByFields, function (e, i) { - localizationService.localize(e.key).then(function (v) { - e.name = v; + //Watch the prevalues + $scope.$watch(function () { + return _.findWhere(getPrevalues(), { key: "includeProperties" }).value; + }, function () { + populateFields(); + }, true); //Use deep watching, otherwise we won't pick up header changes - switch (e.value) { - case "Updater": - e.name += " (Content only)"; - break; - case "Published": - e.name += " (Content only)"; - break; - case "Email": - e.name += " (Members only)"; - break; - case "Username": - e.name += " (Members only)"; - break; + function populateFields() { + // Helper to find a particular value from the list of sort by options + function findFromSortByFields(value) { + return _.find($scope.sortByFields, function (e) { + return e.value.toLowerCase() === value.toLowerCase(); + }); + } + + // Get list of properties assigned as columns of the list view + var propsPreValue = _.findWhere(getPrevalues(), { key: "includeProperties" }); + + // Populate list of options for the default sort (all the columns plus then node name) + $scope.sortByFields = []; + $scope.sortByFields.push({ value: "name", name: "Name", isSystem: 1 }); + if (propsPreValue != undefined) { + for (var i = 0; i < propsPreValue.value.length; i++) { + var value = propsPreValue.value[i]; + $scope.sortByFields.push({ + value: value.alias, + name: value.header, + isSystem: value.isSystem + }); } - }); - }); + } + // Localize the system fields, for some reason the directive doesn't work inside of the select group with an ng-model declared + var systemFields = [ + { value: "SortOrder", key: "general_sort" }, + { value: "Name", key: "general_name" }, + { value: "VersionDate", key: "content_updateDate" }, + { value: "Updater", key: "content_updatedBy" }, + { value: "CreateDate", key: "content_createDate" }, + { value: "Owner", key: "content_createBy" }, + { value: "ContentTypeAlias", key: "content_documentType" }, + { value: "Published", key: "content_isPublished" }, + { value: "Email", key: "general_email" }, + { value: "Username", key: "general_username" } + ]; + _.each(systemFields, function (e) { + localizationService.localize(e.key).then(function (v) { + + var sortByListValue = findFromSortByFields(e.value); + if (sortByListValue) { + sortByListValue.name = v; + switch (e.value) { + case "Updater": + e.name += " (Content only)"; + break; + case "Published": + e.name += " (Content only)"; + break; + case "Email": + e.name += " (Members only)"; + break; + case "Username": + e.name += " (Members only)"; + break; + } + } + }); + }); + + // Check existing model value is available in list and ensure a value is set + var existingValue = findFromSortByFields($scope.model.value); + if (existingValue) { + // Set the existing value + // The old implementation pre Umbraco 7.5 used PascalCase aliases, this uses camelCase, so this ensures that any previous value is set + $scope.model.value = existingValue.value; + } + else { + // Existing value not found, set to first value + $scope.model.value = $scope.sortByFields[0].value; + } + } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html index 2b35aa2ca4..754afd9b60 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/sortby.prevalues.html @@ -1,5 +1,4 @@  \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js index 7b3d5698c5..53424a7d6e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/markdowneditor/markdowneditor.controller.js @@ -57,9 +57,11 @@ function MarkdownEditorController($scope, $element, assetsService, dialogService }); editor2.hooks.set("onPreviewRefresh", function () { - angularHelper.getCurrentForm($scope).$setDirty(); - // We must manually update the model as there is no way to hook into the markdown editor events without editing that code. - $scope.model.value = $("textarea", $element).val(); + // We must manually update the model as there is no way to hook into the markdown editor events without exstensive edits to the library. + if ($scope.model.value !== $("textarea", $element).val()) { + angularHelper.getCurrentForm($scope).$setDirty(); + $scope.model.value = $("textarea", $element).val(); + } }); }, 200); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index 9969af937d..bbf61a32d3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -6,6 +6,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl //check the pre-values for multi-picker var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; + var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false; if (!$scope.model.config.startNodeId) { userService.getCurrentUser().then(function (userData) { @@ -68,6 +69,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl startNodeId: $scope.model.config.startNodeId, multiPicker: multiPicker, onlyImages: onlyImages, + disableFolderSelect: disableFolderSelect, show: true, submit: function(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html index 096685f1a9..3cb01386ba 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html @@ -3,10 +3,16 @@