Compare commits
492 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2dc11f6fa1 | |||
| fe2e689865 | |||
| ece5fecf12 | |||
| b21dea84d9 | |||
| 492b0aadc8 | |||
| b399630202 | |||
| b9b1fc3a70 | |||
| 1d52f59657 | |||
| 78f6b8d8bc | |||
| 0afee71e9e | |||
| ffa7b922e8 | |||
| e2c2b8534f | |||
| cfd3cc5187 | |||
| e6de9e0004 | |||
| 1f29418ea5 | |||
| 977b30c51a | |||
| 66a849dd12 | |||
| 637abc384b | |||
| 8959b81f6a | |||
| 32bb8ac5a4 | |||
| 016caba35f | |||
| 9290f61f94 | |||
| d522dd7a31 | |||
| 95f632e1ea | |||
| e72ec171f7 | |||
| 2538e2e9c9 | |||
| 08aff2654a | |||
| cd0fdae47b | |||
| 0f3a76809b | |||
| 4f88da2c26 | |||
| b1ceeb23b3 | |||
| 46d0807fa4 | |||
| 9a94ac5317 | |||
| 0b904e82b5 | |||
| c816558936 | |||
| 4ce0553630 | |||
| b1b285b673 | |||
| c773714e2d | |||
| 51fec0c7d3 | |||
| 2a90478ba7 | |||
| 87ace12a4f | |||
| 93756a62f4 | |||
| 2343004be1 | |||
| d6fe350a4a | |||
| 23321f8ac9 | |||
| 71902f3b9b | |||
| 13b544521d | |||
| 2cf8c1207d | |||
| 632f84904f | |||
| a1fd73cdb0 | |||
| a53866953e | |||
| ff996b05f0 | |||
| 403502ef1b | |||
| ee4ad36e70 | |||
| 4f2650277a | |||
| 0231a6037e | |||
| db51a5bb44 | |||
| d1b7063e44 | |||
| 364d4b11e8 | |||
| c1769aa299 | |||
| a571eee945 | |||
| ab8653d3de | |||
| 143d127fb9 | |||
| 748a3c167e | |||
| 01cf2e240a | |||
| 49689d5057 | |||
| f722d7731d | |||
| 5b93b35882 | |||
| b29ee4df60 | |||
| d8fe3c6faf | |||
| 74ba3261ff | |||
| ac42f335c2 | |||
| f0eefbd5af | |||
| ed48f29e3a | |||
| 03c5d5197b | |||
| 7f0921bd96 | |||
| c38b49b1cf | |||
| c4dfbd4a1c | |||
| 9b1e5d7649 | |||
| c07b96ade7 | |||
| 576a790e2f | |||
| 9b7a9fc988 | |||
| 62485157ad | |||
| 4ce7b089ca | |||
| 9e91098ebd | |||
| 49356b31a1 | |||
| 29f93160b7 | |||
| 507c2353e9 | |||
| 827e79c705 | |||
| d02139bbcd | |||
| 8a19b97900 | |||
| 981fad1ec8 | |||
| 019147c1a1 | |||
| 782d6100f0 | |||
| f6ea317dd1 | |||
| 68e8ca8d8e | |||
| bcae131545 | |||
| c6d686eabd | |||
| a8b660bfbf | |||
| 1f60486df8 | |||
| c066280990 | |||
| ba747ea579 | |||
| 6f857b16ee | |||
| 27115e5794 | |||
| 13e6a81bbe | |||
| bd2a794b59 | |||
| 576ffce9aa | |||
| 3bfdaa7e10 | |||
| dc7b5bae9b | |||
| 422be31c4e | |||
| 8d8fb76694 | |||
| 80d605beac | |||
| bb9a0258df | |||
| 69258fee7b | |||
| 1fd7569c6e | |||
| 3a1f19ee0a | |||
| 90bc7f5829 | |||
| 30b391f3fb | |||
| 089f488c15 | |||
| 99b99ce3cd | |||
| 10ca1239b6 | |||
| f4fc3c4db4 | |||
| e17690fbed | |||
| 234c0680d8 | |||
| ea8c6af028 | |||
| d7d1b3f2a0 | |||
| 5f31c70cbe | |||
| 579a04d179 | |||
| ff3b7963b1 | |||
| e85d212891 | |||
| 5b19d5e0d8 | |||
| 7c21889239 | |||
| 52d35c06eb | |||
| 52c3ea24d5 | |||
| 03756ab894 | |||
| ef40ecfefd | |||
| 8e35d904cb | |||
| 9808492509 | |||
| 81788338e3 | |||
| 3ffe1517e0 | |||
| 641e74b747 | |||
| 49c316e13d | |||
| 5235053523 | |||
| af6d2df823 | |||
| bbe80d882a | |||
| 408ec17201 | |||
| a4ef487fab | |||
| 8208f8de51 | |||
| e8ea7f9d29 | |||
| e431a41fc1 | |||
| 84b380028c | |||
| 1f5c9d2e1b | |||
| 372f981d33 | |||
| 6f03e221c6 | |||
| 90a5adec8f | |||
| 62939385a0 | |||
| d4729e1483 | |||
| 9de35f0202 | |||
| 773d249d36 | |||
| d12a492cb6 | |||
| ef0b521083 | |||
| 6261447923 | |||
| d18881c8ad | |||
| 89596dbe0b | |||
| 358536ffaa | |||
| 4592f8e8d8 | |||
| 0038d173a2 | |||
| 68e6ad76fb | |||
| 6462329494 | |||
| 2fdd1c0bd2 | |||
| 006deb22db | |||
| 40624255ca | |||
| 891bea81bf | |||
| f71577f86e | |||
| e3e88b3679 | |||
| 5dc993890d | |||
| a2025c14b1 | |||
| 24900fb9f8 | |||
| 594c473134 | |||
| 34f811c128 | |||
| 7e4c284abb | |||
| 664d0c5160 | |||
| eb26fb901f | |||
| 3141b04376 | |||
| 201fb5dc01 | |||
| 15e668c056 | |||
| a42f425971 | |||
| 3f4774855e | |||
| 28f995cea6 | |||
| cfb14d7089 | |||
| ef4d41c80a | |||
| 584e47f51f | |||
| 66e40fcbb0 | |||
| 49d54f8ca5 | |||
| 4ed236249a | |||
| 00a4287945 | |||
| 92d52bf99b | |||
| 2e611c20d4 | |||
| ae10f3d21f | |||
| 7a9b392f69 | |||
| 493d71dba9 | |||
| 55d4e4072a | |||
| f687b34c14 | |||
| 507517c1ad | |||
| ae8cfb2b23 | |||
| 731e380445 | |||
| 9048252826 | |||
| e76819a570 | |||
| 87c311d6e7 | |||
| 86d8eae984 | |||
| 6aac216763 | |||
| b7c3fb7f30 | |||
| a0a3b7e9c9 | |||
| d98db5e6bc | |||
| 48adc4c98d | |||
| bd0edb322d | |||
| 79d4562cb1 | |||
| f48263e5a2 | |||
| 63196d7e53 | |||
| 72a0d614af | |||
| f766057e50 | |||
| f0b5c831f1 | |||
| d3232a09fb | |||
| 586c59949a | |||
| 7cbaecc53a | |||
| 4f8bb7fb51 | |||
| 1c883be16b | |||
| 73d681d674 | |||
| e84a963d66 | |||
| 8daa558997 | |||
| b78fd0f6ac | |||
| 58c180d6cc | |||
| 831eb3ef5e | |||
| fa68bee762 | |||
| 796700b4fe | |||
| ab09042fdc | |||
| 041d4a4a8b | |||
| ee79d7ecdc | |||
| 18e377f159 | |||
| e33456d168 | |||
| df2c104002 | |||
| f265fa683c | |||
| 7e7f9902f6 | |||
| a36184a261 | |||
| dcd2630d6b | |||
| b70f543790 | |||
| 24c1c15b66 | |||
| 4a0cd7228b | |||
| 12093ae7ea | |||
| 6a980e64eb | |||
| edfe212227 | |||
| 37071ada78 | |||
| 3c7e820773 | |||
| 203837aa84 | |||
| e3bc98a155 | |||
| f85532f15e | |||
| 5fe63c2c1a | |||
| 2c1d85989f | |||
| d518ab908e | |||
| bc32ff31d4 | |||
| 7804677f2b | |||
| b04d71ca5f | |||
| 27c884f4eb | |||
| ab4de382f3 | |||
| a293c633fc | |||
| 507e3ece98 | |||
| b77826fd3d | |||
| 4c7358d63b | |||
| 4814612102 | |||
| 6dae4396bb | |||
| a966f63bbd | |||
| 76f9e6baee | |||
| dd5560f00b | |||
| b56575fc50 | |||
| b06d30c0f9 | |||
| 49f17be9df | |||
| 9c50e1c48d | |||
| 0688835ac0 | |||
| 353b8f21cd | |||
| 30a0d553d5 | |||
| d0fbc362b2 | |||
| 39c534ef54 | |||
| 7f86166abf | |||
| 838e86469a | |||
| 24b147e2a6 | |||
| 11342ff1df | |||
| bf20af9345 | |||
| c69774e3f2 | |||
| 7de8f5a59a | |||
| 6795afd5c2 | |||
| 9411a22a0d | |||
| 461ce64feb | |||
| 53c251feb5 | |||
| c5a55b17e6 | |||
| fd577afe2c | |||
| f46ef0b006 | |||
| 8a114a105e | |||
| 909e3788ea | |||
| 09a92ef6ce | |||
| 7d9c2bcce9 | |||
| 529ae961ff | |||
| 04f64cd804 | |||
| dc937ec942 | |||
| 5f9865cc73 | |||
| cefd9e7323 | |||
| 315690e758 | |||
| fb8c519f71 | |||
| 80f172e3fb | |||
| 5eced19816 | |||
| 5df4d3509d | |||
| b891b18b20 | |||
| 4bd263e7bc | |||
| bc461b1646 | |||
| b0eeaecf1c | |||
| b094bfad29 | |||
| e333f6c628 | |||
| 2708f11fcd | |||
| 916bc6badb | |||
| 76ee23b933 | |||
| 2a0cc71d06 | |||
| fd8daa398f | |||
| c59ac4ee0e | |||
| a03aa9cbab | |||
| a2e6da2491 | |||
| ba7d8b0955 | |||
| 7288bcf679 | |||
| ca57b9c5e6 | |||
| 62ce7f6132 | |||
| b1f2c5d93a | |||
| 1ec07aa205 | |||
| 13055ad1f2 | |||
| 89b714c960 | |||
| d9b0b0112e | |||
| d5447106a6 | |||
| c5d874464a | |||
| 6640039cdf | |||
| 70deffd418 | |||
| 1cc3b39601 | |||
| f31d5b12da | |||
| 7fd6155807 | |||
| 043c677ddc | |||
| f634d9f8b1 | |||
| db637d9c31 | |||
| 24b521881c | |||
| 0b3a38ec8f | |||
| 330673bef9 | |||
| 661ff0e726 | |||
| e1c45fda9a | |||
| b45a4a18f2 | |||
| c5f4793d11 | |||
| 4175c0530f | |||
| 1456e7028a | |||
| b38ee33e5a | |||
| 903a9f93c3 | |||
| b08376651a | |||
| 75514c7c42 | |||
| ea6f561c96 | |||
| cf97926f26 | |||
| 4b08c3a5c3 | |||
| fbda4fc8d1 | |||
| 312b994537 | |||
| dd1bfe9cbf | |||
| de17b69564 | |||
| 7f44f4a030 | |||
| 422219ab21 | |||
| 9986a3c2c2 | |||
| 053544a578 | |||
| ce81416368 | |||
| 04e04ae47a | |||
| 4e6d60b755 | |||
| 0f6e7d26c9 | |||
| 51ceba6fea | |||
| 5dde2efe0d | |||
| 4308718c88 | |||
| fe2b86b681 | |||
| 368fec4ac1 | |||
| 1b59ca0412 | |||
| 282cc0c722 | |||
| 9016147118 | |||
| bee75803f7 | |||
| 7b3f7f4ad4 | |||
| f6c25f29c0 | |||
| 6de3b920eb | |||
| 4584abaa72 | |||
| c1ca0c9c2f | |||
| 27c260cd03 | |||
| 73a5c55547 | |||
| b8831c253e | |||
| b2d3e75566 | |||
| cb526170e2 | |||
| 93237bfdb8 | |||
| 1c39c741b6 | |||
| ec2589f517 | |||
| 2ca6e8be9a | |||
| b9d1a776cb | |||
| 5eb609d8a0 | |||
| 4d5069726b | |||
| 0da8644e77 | |||
| e643189b10 | |||
| 97716d5570 | |||
| 5517252200 | |||
| 92a5c31ce6 | |||
| 345c038e8b | |||
| 59defa6b11 | |||
| 7e0d7ebbd4 | |||
| c13d4ed235 | |||
| a47b5035fe | |||
| f6be389f4f | |||
| 252f0fe88e | |||
| 387faca71e | |||
| 62cda02ded | |||
| 3e2a6424bd | |||
| c0f695bcf9 | |||
| 2e1e2e4325 | |||
| 1316bb5d5b | |||
| fe97860694 | |||
| 98b4aa6bae | |||
| eb7f4e74f2 | |||
| 4a9e326267 | |||
| 67edc6cf9a | |||
| e740838ef7 | |||
| dec44b1288 | |||
| 98a0729549 | |||
| 97868593f8 | |||
| dcdfb074af | |||
| a36b1097c5 | |||
| e268a05f6e | |||
| fb7d22dca8 | |||
| bac53bc241 | |||
| ce690acda0 | |||
| cb8157dfca | |||
| 4c7c6e37d1 | |||
| 116e53e07d | |||
| a74c4433f7 | |||
| 2ccf74a921 | |||
| f3a81d82be | |||
| 5451d28fe4 | |||
| db57268440 | |||
| ac265067e8 | |||
| 5330830362 | |||
| 4b6b24b2f5 | |||
| 7d5d89e9cb | |||
| ab85cbf599 | |||
| c6937618c2 | |||
| ef84d63982 | |||
| 280a7445f4 | |||
| 5ae8a47f4b | |||
| bc148d08fa | |||
| 57a1e73383 | |||
| c7b9a57795 | |||
| eb1edf94d3 | |||
| 606abd8ac7 | |||
| c0b230a95e | |||
| 607c2cc8e7 | |||
| 48d3a3defd | |||
| 8509285aae | |||
| 2413e9951f | |||
| 1a444fd703 | |||
| bd68a140ba | |||
| 7b34211bb1 | |||
| fa78223229 | |||
| 39c19dc1d4 | |||
| 7b4b6357cb | |||
| 4454290820 | |||
| d94cd9dc8d | |||
| 8c07af9505 | |||
| d32fb44187 | |||
| dd38480ce2 | |||
| 08e8e3918e | |||
| 7a3591d1bd | |||
| 59ab7f5c8d | |||
| c2b2182182 | |||
| 66b656f058 | |||
| 65db5396cf | |||
| 32d5a0a57c | |||
| c8f7ecabf4 | |||
| 9137249510 | |||
| 7367c8ef67 | |||
| ed480e1e6e | |||
| 841074fc27 | |||
| be1172d604 | |||
| 7cf8cd3518 | |||
| 4cab1c89b4 | |||
| 7b08c0dde6 | |||
| 88fd5a9623 | |||
| 361ef2a475 | |||
| d382fcc919 | |||
| aac8451627 | |||
| c96c09f42f | |||
| 50f8f18503 | |||
| 9001cd8a6f | |||
| 1279fd234f |
@@ -14,6 +14,43 @@ By default, this builds the current version. It is possible to specify a differe
|
||||
|
||||
Valid version strings are defined in the `Set-UmbracoVersion` documentation below.
|
||||
|
||||
## PowerShell Quirks
|
||||
|
||||
There is a good chance that running `build.ps1` ends up in error, with messages such as
|
||||
|
||||
>The file ...\build\build.ps1 is not digitally signed. You cannot run this script on the current system. For more information about running scripts and setting execution policy, see about_Execution_Policies.
|
||||
|
||||
PowerShell has *Execution Policies* that may prevent the script from running. You can check the current policies with:
|
||||
|
||||
PS> Get-ExecutionPolicy -List
|
||||
|
||||
Scope ExecutionPolicy
|
||||
----- ---------------
|
||||
MachinePolicy Undefined
|
||||
UserPolicy Undefined
|
||||
Process Undefined
|
||||
CurrentUser Undefined
|
||||
LocalMachine RemoteSigned
|
||||
|
||||
Policies can be `Restricted`, `AllSigned`, `RemoteSigned`, `Unrestricted` and `Bypass`. Scopes can be `MachinePolicy`, `UserPolicy`, `Process`, `CurrentUser`, `LocalMachine`. You need the current policy to be `RemoteSigned`—as long as it is `Undefined`, the script cannot run. You can change the current user policy with:
|
||||
|
||||
PS> Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
|
||||
|
||||
Alternatively, you can do it at machine level, from within an elevated PowerShell session:
|
||||
|
||||
PS> Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy RemoteSigned
|
||||
|
||||
And *then* the script should run. It *might* however still complain about executing scripts, with messages such as:
|
||||
|
||||
>Security warning - Run only scripts that you trust. While scripts from the internet can be useful, this script can potentially harm your computer. If you trust this script, use the Unblock-File cmdlet to allow the script to run without this warning message. Do you want to run ...\build\build.ps1?
|
||||
[D] Do not run [R] Run once [S] Suspend [?] Help (default is "D"):
|
||||
|
||||
This is usually caused by the scripts being *blocked*. And that usually happens when the source code has been downloaded as a Zip file. When Windows downloads Zip files, they are marked as *blocked* (technically, they have a Zone.Identifier alternate data stream, with a value of "3" to indicate that they were downloaded from the Internet). And when such a Zip file is un-zipped, each and every single file is also marked as blocked.
|
||||
|
||||
The best solution is to unblock the Zip file before un-zipping: right-click the files, open *Properties*, and there should be a *Unblock* checkbox at the bottom of the dialog. If, however, the Zip file has already been un-zipped, it is possible to recursively unblock all files from PowerShell with:
|
||||
|
||||
PS> Get-ChildItem -Recurse *.* | Unblock-File
|
||||
|
||||
## Notes
|
||||
|
||||
Git might have issues dealing with long file paths during build. You may want/need to enable `core.longpaths` support (see [this page](https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path) for details).
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Umbraco CMS
|
||||
===========
|
||||
The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 390,000 websites worldwide: [https://umbraco.com](https://umbraco.com)
|
||||
The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 443,000 websites worldwide: [https://umbraco.com](https://umbraco.com)
|
||||
|
||||
[](https://vimeo.com/172382998/)
|
||||
|
||||
@@ -12,7 +12,7 @@ Umbraco is a free open source Content Management System built on the ASP.NET pla
|
||||
|
||||
## Building Umbraco from source ##
|
||||
|
||||
The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`.
|
||||
The easiest way to get started is to run `build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details.
|
||||
|
||||
Note that you can always [download a nightly build](http://nightly.umbraco.org/?container=umbraco-750) so you don't have to build the code yourself.
|
||||
|
||||
@@ -26,7 +26,7 @@ For the first time on the Microsoft platform, there is a free user and developer
|
||||
|
||||
Umbraco is not only loved by developers, but is a content editors dream. Enjoy intuitive editing tools, media management, responsive views and approval workflows to send your content live.
|
||||
|
||||
Used by more than 350,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scales. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 200,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay.
|
||||
Used by more than 443,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scales. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 220,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay.
|
||||
|
||||
To view more examples, please visit [https://umbraco.com/why-umbraco/#caseStudies](https://umbraco.com/why-umbraco/#caseStudies)
|
||||
|
||||
|
||||
@@ -10,6 +10,5 @@ IF ERRORLEVEL 1 (
|
||||
:error
|
||||
ECHO.
|
||||
ECHO Can not run build\build.ps1.
|
||||
ECHO If this is due to a SecurityError then make sure to run the following command from an administrator command prompt:
|
||||
ECHO If this is due to a SecurityError then please refer to BUILD.md for help!
|
||||
ECHO.
|
||||
ECHO powershell Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
|
||||
@@ -40,6 +40,7 @@ function Get-UmbracoBuildEnv
|
||||
&$nuget install 7-Zip.CommandLine -OutputDirectory $path -Verbosity quiet
|
||||
$dir = ls "$path\7-Zip.CommandLine.*" | sort -property Name -descending | select -first 1
|
||||
$file = ls -path "$dir" -name 7za.exe -recurse
|
||||
$file = ls -path "$dir" -name 7za.exe -recurse | select -first 1 #A select is because there is tools\7za.exe & tools\x64\7za.exe
|
||||
mv "$dir\$file" $sevenZip
|
||||
Remove-Directory $dir
|
||||
}
|
||||
|
||||
@@ -27,15 +27,14 @@
|
||||
<dependency id="MiniProfiler" version="[2.1.0, 3.0.0)" />
|
||||
<dependency id="HtmlAgilityPack" version="[1.4.9.5, 2.0.0)" />
|
||||
<dependency id="Lucene.Net" version="[2.9.4.1, 3.0.0.0)" />
|
||||
<dependency id="SharpZipLib" version="[0.86.0, 1.0.0)" />
|
||||
<dependency id="MySql.Data" version="[6.9.9, 7.0.0)" />
|
||||
<dependency id="ClientDependency" version="[1.9.2, 2.0.0)" />
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.8.0.0, 2.0.0)" />
|
||||
<dependency id="AutoMapper" version="[3.3.1, 4.0.0)" />
|
||||
<dependency id="Newtonsoft.Json" version="[10.0.2, 11.0.0)" />
|
||||
<dependency id="Examine" version="[0.1.85, 1.0.0)" />
|
||||
<dependency id="ImageProcessor" version="[2.5.3, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web" version="[4.8.3, 5.0.0)" />
|
||||
<dependency id="ImageProcessor" version="[2.5.6, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web" version="[4.8.7, 5.0.0)" />
|
||||
<dependency id="semver" version="[1.1.2, 3.0.0)" />
|
||||
<!-- Markdown can not be updated due to: https://github.com/hey-red/markdownsharp/issues/71#issuecomment-233585487 -->
|
||||
<dependency id="Markdown" version="[1.14.7, 2.0.0)" />
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<dependency id="Newtonsoft.Json" version="[10.0.2, 11.0.0)" />
|
||||
<dependency id="Umbraco.ModelsBuilder" version="[3.0.7, 4.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.2.1, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web.Config" version="[2.3.0, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web.Config" version="[2.3.1, 3.0.0)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
|
||||
+2
-2
@@ -11,5 +11,5 @@ using System.Resources;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
[assembly: AssemblyFileVersion("7.7.2")]
|
||||
[assembly: AssemblyInformationalVersion("7.7.2")]
|
||||
[assembly: AssemblyFileVersion("7.8.0")]
|
||||
[assembly: AssemblyInformationalVersion("7.8.0-beta")]
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Threading;
|
||||
using Semver;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.ObjectResolution;
|
||||
@@ -341,6 +342,52 @@ namespace Umbraco.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Current Version of the Umbraco Site before an upgrade
|
||||
/// by using the last/most recent Umbraco Migration that has been run
|
||||
/// </summary>
|
||||
/// <returns>A SemVersion of the latest Umbraco DB Migration run</returns>
|
||||
/// <remarks>
|
||||
/// NOTE: This existed in the InstallHelper previously but should really be here so it can be re-used if necessary
|
||||
/// </remarks>
|
||||
internal SemVersion CurrentVersion()
|
||||
{
|
||||
//Set a default version of 0.0.0
|
||||
var version = new SemVersion(0);
|
||||
|
||||
//If we have a db context available, if we don't then we are not installed anyways
|
||||
if (DatabaseContext.IsDatabaseConfigured && DatabaseContext.CanConnect)
|
||||
version = DatabaseContext.ValidateDatabaseSchema().DetermineInstalledVersionByMigrations(Services.MigrationEntryService);
|
||||
|
||||
if (version != new SemVersion(0))
|
||||
return version;
|
||||
|
||||
// If we aren't able to get a result from the umbracoMigrations table then use the version in web.config, if it's available
|
||||
if (string.IsNullOrWhiteSpace(GlobalSettings.ConfigurationStatus))
|
||||
return version;
|
||||
|
||||
var configuredVersion = GlobalSettings.ConfigurationStatus;
|
||||
|
||||
string currentComment = null;
|
||||
|
||||
var current = configuredVersion.Split('-');
|
||||
if (current.Length > 1)
|
||||
currentComment = current[1];
|
||||
|
||||
Version currentVersion;
|
||||
if (Version.TryParse(current[0], out currentVersion))
|
||||
{
|
||||
version = new SemVersion(
|
||||
currentVersion.Major,
|
||||
currentVersion.Minor,
|
||||
currentVersion.Build,
|
||||
string.IsNullOrWhiteSpace(currentComment) ? null : currentComment,
|
||||
currentVersion.Revision > 0 ? currentVersion.Revision.ToString() : null);
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
private void AssertIsNotReady()
|
||||
{
|
||||
if (this.IsReady)
|
||||
|
||||
@@ -538,24 +538,41 @@ namespace Umbraco.Core.Configuration
|
||||
|
||||
internal static bool ContentCacheXmlStoredInCodeGen
|
||||
{
|
||||
get { return ContentCacheXmlStorageLocation == ContentXmlStorage.AspNetTemp; }
|
||||
get { return LocalTempStorageLocation == LocalTempStorage.AspNetTemp; }
|
||||
}
|
||||
|
||||
internal static ContentXmlStorage ContentCacheXmlStorageLocation
|
||||
|
||||
/// <summary>
|
||||
/// This is the location type to store temporary files such as cache files or other localized files for a given machine
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Currently used for the xml cache file and the plugin cache files
|
||||
/// </remarks>
|
||||
internal static LocalTempStorage LocalTempStorageLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
{
|
||||
//there's a bunch of backwards compat config checks here....
|
||||
|
||||
//This is the current one
|
||||
if (ConfigurationManager.AppSettings.ContainsKey("umbracoLocalTempStorage"))
|
||||
{
|
||||
return Enum<LocalTempStorage>.Parse(ConfigurationManager.AppSettings["umbracoLocalTempStorage"]);
|
||||
}
|
||||
|
||||
//This one is old
|
||||
if (ConfigurationManager.AppSettings.ContainsKey("umbracoContentXMLStorage"))
|
||||
{
|
||||
return Enum<ContentXmlStorage>.Parse(ConfigurationManager.AppSettings["umbracoContentXMLStorage"]);
|
||||
}
|
||||
return Enum<LocalTempStorage>.Parse(ConfigurationManager.AppSettings["umbracoContentXMLStorage"]);
|
||||
}
|
||||
|
||||
//This one is older
|
||||
if (ConfigurationManager.AppSettings.ContainsKey("umbracoContentXMLUseLocalTemp"))
|
||||
{
|
||||
return bool.Parse(ConfigurationManager.AppSettings["umbracoContentXMLUseLocalTemp"])
|
||||
? ContentXmlStorage.AspNetTemp
|
||||
: ContentXmlStorage.Default;
|
||||
? LocalTempStorage.AspNetTemp
|
||||
: LocalTempStorage.Default;
|
||||
}
|
||||
return ContentXmlStorage.Default;
|
||||
return LocalTempStorage.Default;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
namespace Umbraco.Core.Configuration
|
||||
{
|
||||
internal enum ContentXmlStorage
|
||||
internal enum LocalTempStorage
|
||||
{
|
||||
Default,
|
||||
AspNetTemp,
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Configuration;
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
internal class BackOfficeElement : UmbracoConfigurationElement, IBackOfficeSection
|
||||
{
|
||||
[ConfigurationProperty("tours")]
|
||||
internal TourConfigElement Tours
|
||||
{
|
||||
get { return (TourConfigElement)this["tours"]; }
|
||||
}
|
||||
|
||||
ITourSection IBackOfficeSection.Tours
|
||||
{
|
||||
get { return Tours; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
public interface IBackOfficeSection
|
||||
{
|
||||
ITourSection Tours { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
public interface ITourSection
|
||||
{
|
||||
bool EnableTours { get; }
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
public interface IUmbracoSettingsSection : IUmbracoConfigurationSection
|
||||
{
|
||||
IBackOfficeSection BackOffice { get; }
|
||||
|
||||
IContentSection Content { get; }
|
||||
|
||||
ISecuritySection Security { get; }
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Configuration;
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
internal class TourConfigElement : UmbracoConfigurationElement, ITourSection
|
||||
{
|
||||
//disabled by default so that upgraders don't get it enabled by default
|
||||
//TODO: we probably just want to disable the initial one from automatically loading ?
|
||||
[ConfigurationProperty("enable", DefaultValue = false)]
|
||||
public bool EnableTours
|
||||
{
|
||||
get { return (bool)this["enable"]; }
|
||||
}
|
||||
|
||||
//TODO: We could have additional filters, etc... defined here
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
|
||||
public class UmbracoSettingsSection : ConfigurationSection, IUmbracoSettingsSection
|
||||
{
|
||||
[ConfigurationProperty("backOffice")]
|
||||
internal BackOfficeElement BackOffice
|
||||
{
|
||||
get { return (BackOfficeElement)this["backOffice"]; }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("content")]
|
||||
internal ContentElement Content
|
||||
{
|
||||
@@ -149,6 +155,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
get { return Templates; }
|
||||
}
|
||||
|
||||
IBackOfficeSection IUmbracoSettingsSection.BackOffice
|
||||
{
|
||||
get { return BackOffice; }
|
||||
}
|
||||
|
||||
IDeveloperSection IUmbracoSettingsSection.Developer
|
||||
{
|
||||
get { return Developer; }
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
|
||||
{
|
||||
public class UmbracoVersion
|
||||
{
|
||||
private static readonly Version Version = new Version("7.7.2");
|
||||
private static readonly Version Version = new Version("7.8.0");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current version of Umbraco.
|
||||
|
||||
@@ -26,13 +26,13 @@ namespace Umbraco.Core
|
||||
var ipAddress = httpContext.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
|
||||
|
||||
if (string.IsNullOrEmpty(ipAddress))
|
||||
return httpContext.Request.ServerVariables["REMOTE_ADDR"];
|
||||
return httpContext.Request.UserHostAddress;
|
||||
|
||||
var addresses = ipAddress.Split(',');
|
||||
if (addresses.Length != 0)
|
||||
return addresses[0];
|
||||
|
||||
return httpContext.Request.ServerVariables["REMOTE_ADDR"];
|
||||
return httpContext.Request.UserHostAddress;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
|
||||
@@ -13,11 +13,28 @@ using Umbraco.Core.Configuration;
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
public static class IOHelper
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value forcing Umbraco to consider it is non-hosted.
|
||||
/// </summary>
|
||||
/// <remarks>This should always be false, unless unit testing.</remarks>
|
||||
public static bool ForceNotHosted { get; set; }
|
||||
|
||||
private static string _rootDir = "";
|
||||
|
||||
// static compiled regex for faster performance
|
||||
private readonly static Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether Umbraco is hosted.
|
||||
/// </summary>
|
||||
public static bool IsHosted
|
||||
{
|
||||
get
|
||||
{
|
||||
return ForceNotHosted == false && (HttpContext.Current != null || HostingEnvironment.IsHosted);
|
||||
}
|
||||
}
|
||||
|
||||
public static char DirSepChar
|
||||
{
|
||||
@@ -72,14 +89,14 @@ namespace Umbraco.Core.IO
|
||||
internal static string ResolveUrlsFromTextString(string text)
|
||||
{
|
||||
if (UmbracoConfig.For.UmbracoSettings().Content.ResolveUrlsFromTextString)
|
||||
{
|
||||
{
|
||||
using (DisposableTimer.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete"))
|
||||
{
|
||||
// find all relative urls (ie. urls that contain ~)
|
||||
var tags = ResolveUrlPattern.Matches(text);
|
||||
|
||||
|
||||
foreach (Match tag in tags)
|
||||
{
|
||||
{
|
||||
string url = "";
|
||||
if (tag.Groups[1].Success)
|
||||
url = tag.Groups[1].Value;
|
||||
@@ -97,7 +114,8 @@ namespace Umbraco.Core.IO
|
||||
|
||||
public static string MapPath(string path, bool useHttpContext)
|
||||
{
|
||||
if (path == null) throw new ArgumentNullException("path");
|
||||
if (path == null) throw new ArgumentNullException("path");
|
||||
useHttpContext = useHttpContext && IsHosted;
|
||||
|
||||
// Check if the path is already mapped
|
||||
if ((path.Length >= 2 && path[1] == Path.VolumeSeparatorChar)
|
||||
@@ -303,7 +321,7 @@ namespace Umbraco.Core.IO
|
||||
var debugFolder = Path.Combine(binFolder, "debug");
|
||||
if (Directory.Exists(debugFolder))
|
||||
return debugFolder;
|
||||
#endif
|
||||
#endif
|
||||
var releaseFolder = Path.Combine(binFolder, "release");
|
||||
if (Directory.Exists(releaseFolder))
|
||||
return releaseFolder;
|
||||
|
||||
@@ -73,19 +73,19 @@ namespace Umbraco.Core.IO
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (GlobalSettings.ContentCacheXmlStorageLocation)
|
||||
switch (GlobalSettings.LocalTempStorageLocation)
|
||||
{
|
||||
case ContentXmlStorage.AspNetTemp:
|
||||
case LocalTempStorage.AspNetTemp:
|
||||
return Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco.config");
|
||||
case ContentXmlStorage.EnvironmentTemp:
|
||||
case LocalTempStorage.EnvironmentTemp:
|
||||
var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1();
|
||||
var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoXml",
|
||||
//include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back
|
||||
// to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not
|
||||
// utilizing an old path
|
||||
var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData",
|
||||
//include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back
|
||||
// to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not
|
||||
// utilizing an old path
|
||||
appDomainHash);
|
||||
return Path.Combine(cachePath, "umbraco.config");
|
||||
case ContentXmlStorage.Default:
|
||||
case LocalTempStorage.Default:
|
||||
return IOHelper.ReturnPath("umbracoContentXML", "~/App_Data/umbraco.config");
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
@@ -2,18 +2,30 @@
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public sealed class AuditItem : Entity, IAggregateRoot
|
||||
public sealed class AuditItem : Entity, IAuditItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor for creating an item to be created
|
||||
/// </summary>
|
||||
/// <param name="objectId"></param>
|
||||
/// <param name="comment"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="userId"></param>
|
||||
public AuditItem(int objectId, string comment, AuditType type, int userId)
|
||||
{
|
||||
DisableChangeTracking();
|
||||
|
||||
Id = objectId;
|
||||
Comment = comment;
|
||||
AuditType = type;
|
||||
UserId = userId;
|
||||
|
||||
EnableChangeTracking();
|
||||
}
|
||||
|
||||
public string Comment { get; private set; }
|
||||
public AuditType AuditType { get; private set; }
|
||||
public int UserId { get; private set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public interface IAuditItem : IAggregateRoot
|
||||
{
|
||||
string Comment { get; }
|
||||
AuditType AuditType { get; }
|
||||
int UserId { get; }
|
||||
}
|
||||
}
|
||||
@@ -40,19 +40,6 @@ namespace Umbraco.Core.Models.Identity
|
||||
identityUser.EnableChangeTracking();
|
||||
});
|
||||
|
||||
config.CreateMap<BackOfficeIdentityUser, UserData>()
|
||||
.ConstructUsing((BackOfficeIdentityUser user) => new UserData(Guid.NewGuid().ToString("N"))) //this is the 'session id'
|
||||
.ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id))
|
||||
.ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections))
|
||||
.ForMember(detail => detail.Roles, opt => opt.MapFrom(user => user.Roles.Select(x => x.RoleId).ToArray()))
|
||||
.ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name))
|
||||
//When mapping to UserData which is used in the authcookie we want ALL start nodes including ones defined on the groups
|
||||
.ForMember(detail => detail.StartContentNodes, opt => opt.MapFrom(user => user.CalculatedContentStartNodeIds))
|
||||
//When mapping to UserData which is used in the authcookie we want ALL start nodes including ones defined on the groups
|
||||
.ForMember(detail => detail.StartMediaNodes, opt => opt.MapFrom(user => user.CalculatedMediaStartNodeIds))
|
||||
.ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.UserName))
|
||||
.ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.Culture))
|
||||
.ForMember(detail => detail.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp));
|
||||
}
|
||||
|
||||
private string GetPasswordHash(string storedPass)
|
||||
|
||||
@@ -69,6 +69,11 @@ namespace Umbraco.Core.Models.Membership
|
||||
/// <summary>
|
||||
/// Will hold the media file system relative path of the users custom avatar if they uploaded one
|
||||
/// </summary>
|
||||
string Avatar { get; set; }
|
||||
string Avatar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A Json blob stored for recording tour data for a user
|
||||
/// </summary>
|
||||
string TourData { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,7 @@ namespace Umbraco.Core.Models.Membership
|
||||
private string _name;
|
||||
private string _securityStamp;
|
||||
private string _avatar;
|
||||
private string _tourData;
|
||||
private int _sessionTimeout;
|
||||
private int[] _startContentIds;
|
||||
private int[] _startMediaIds;
|
||||
@@ -132,6 +133,7 @@ namespace Umbraco.Core.Models.Membership
|
||||
|
||||
public readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.SecurityStamp);
|
||||
public readonly PropertyInfo AvatarSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.Avatar);
|
||||
public readonly PropertyInfo TourDataSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.TourData);
|
||||
public readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo<User, int>(x => x.SessionTimeout);
|
||||
public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo<User, int[]>(x => x.StartContentIds);
|
||||
public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo<User, int[]>(x => x.StartMediaIds);
|
||||
@@ -464,8 +466,18 @@ namespace Umbraco.Core.Models.Membership
|
||||
{
|
||||
get { return _avatar; }
|
||||
set { SetPropertyValueAndDetectChanges(value, ref _avatar, Ps.Value.AvatarSelector); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A Json blob stored for recording tour data for a user
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string TourData
|
||||
{
|
||||
get { return _tourData; }
|
||||
set { SetPropertyValueAndDetectChanges(value, ref _tourData, Ps.Value.TourDataSelector); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the session timeout.
|
||||
/// </summary>
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Models.Membership
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
internal class UserGroup : Entity, IUserGroup, IReadOnlyUserGroup
|
||||
public class UserGroup : Entity, IUserGroup, IReadOnlyUserGroup
|
||||
{
|
||||
private int? _startContentId;
|
||||
private int? _startMediaId;
|
||||
|
||||
@@ -27,5 +27,10 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Length(500)]
|
||||
public string OriginIdentity { get; set; }
|
||||
|
||||
[Column("instructionCount")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Constraint(Default = 1)]
|
||||
public int InstructionCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -24,4 +24,5 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[ResultColumn]
|
||||
public NodeDto NodeDto { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,6 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[Column("logComment")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(4000)]
|
||||
public string Comment { get; set; }
|
||||
public string Comment { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
[TableName("cmsMedia")]
|
||||
[PrimaryKey("versionId", autoIncrement = false)]
|
||||
[ExplicitColumns]
|
||||
internal class MediaDto
|
||||
{
|
||||
[Column("nodeId")]
|
||||
[ForeignKey(typeof(ContentDto), Column = "nodeId")]
|
||||
[ForeignKey(typeof(NodeDto))]
|
||||
[Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMedia", ForColumns = "nodeId, versionId, mediaPath")]
|
||||
public int NodeId { get; set; }
|
||||
|
||||
[Column("versionId")]
|
||||
[PrimaryKeyColumn(AutoIncrement = false)]
|
||||
public Guid VersionId { get; set; }
|
||||
|
||||
[Column("mediaPath")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public string MediaPath { get; set; }
|
||||
|
||||
[ResultColumn]
|
||||
public ContentVersionDto ContentVersionDto { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[ForeignKey(typeof(PropertyTypeGroupDto))]
|
||||
public int? PropertyTypeGroupId { get; set; }
|
||||
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyTypeAlias")]
|
||||
[Column("Alias")]
|
||||
public string Alias { get; set; }
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
UserGroupDtos = new List<UserGroupDto>();
|
||||
UserStartNodeDtos = new HashSet<UserStartNodeDto>();
|
||||
}
|
||||
}
|
||||
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn(Name = "PK_user")]
|
||||
@@ -40,7 +40,7 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[Column("userPassword")]
|
||||
[Length(500)]
|
||||
public string Password { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This will represent a JSON structure of how the password has been created (i.e hash algorithm, iterations)
|
||||
/// </summary>
|
||||
@@ -95,7 +95,7 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime UpdateDate { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Will hold the media file system relative path of the users custom avatar if they uploaded one
|
||||
/// </summary>
|
||||
@@ -103,6 +103,14 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(500)]
|
||||
public string Avatar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A Json blob stored for recording tour data for a user
|
||||
/// </summary>
|
||||
[Column("tourData")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[SpecialDbType(SpecialDbTypes.NTEXT)]
|
||||
public string TourData { get; set; }
|
||||
|
||||
[ResultColumn]
|
||||
public List<UserGroupDto> UserGroupDtos { get; set; }
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
[TableName("umbracoUserLogin")]
|
||||
[PrimaryKey("sessionId", autoIncrement = false)]
|
||||
[ExplicitColumns]
|
||||
internal class UserLoginDto
|
||||
{
|
||||
[Column("sessionId")]
|
||||
[PrimaryKeyColumn(AutoIncrement = false)]
|
||||
public Guid SessionId { get; set; }
|
||||
|
||||
[Column("userId")]
|
||||
[ForeignKey(typeof(UserDto), Name = "FK_umbracoUserLogin_umbracoUser_id")]
|
||||
public int UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tracks when the session is created
|
||||
/// </summary>
|
||||
[Column("loggedInUtc")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public DateTime LoggedInUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updated every time a user's session is validated
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This allows us to guess if a session is timed out if a user doesn't actively log out
|
||||
/// and also allows us to trim the data in the table
|
||||
/// </remarks>
|
||||
[Column("lastValidatedUtc")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public DateTime LastValidatedUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tracks when the session is removed when the user's account is logged out
|
||||
/// </summary>
|
||||
[Column("loggedOutUtc")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? LoggedOutUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logs the IP address of the session if available
|
||||
/// </summary>
|
||||
[Column("ipAddress")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public string IpAddress { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
[Obsolete("Table no longer exists as of 7.6 - retained only to support migrations from previous versions")]
|
||||
[Obsolete("Table no longer exists as of 7.7 - retained only to support migrations from previous versions")]
|
||||
[TableName("umbracoUserType")]
|
||||
[PrimaryKey("id")]
|
||||
[ExplicitColumns]
|
||||
|
||||
@@ -30,12 +30,11 @@ namespace Umbraco.Core.Models
|
||||
/// Tries to lookup the user's gravatar to see if the endpoint can be reached, if so it returns the valid URL
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="userService"></param>
|
||||
/// <param name="staticCache"></param>
|
||||
/// <returns>
|
||||
/// A list of 5 different sized avatar URLs
|
||||
/// </returns>
|
||||
internal static string[] GetCurrentUserAvatarUrls(this IUser user, IUserService userService, ICacheProvider staticCache)
|
||||
internal static string[] GetUserAvatarUrls(this IUser user, ICacheProvider staticCache)
|
||||
{
|
||||
//check if the user has explicitly removed all avatars including a gravatar, this will be possible and the value will be "none"
|
||||
if (user.Avatar == "none")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace Umbraco.Core.Packaging
|
||||
{
|
||||
@@ -14,17 +14,17 @@ namespace Umbraco.Core.Packaging
|
||||
bool fileFound = false;
|
||||
string foundDir = null;
|
||||
|
||||
ReadZipfileEntries(packageFilePath, (entry, stream) =>
|
||||
ReadZipfileEntries(packageFilePath, entry =>
|
||||
{
|
||||
string fileName = Path.GetFileName(entry.Name);
|
||||
|
||||
if (string.IsNullOrEmpty(fileName) == false &&
|
||||
fileName.Equals(fileToRead, StringComparison.CurrentCultureIgnoreCase))
|
||||
if (string.IsNullOrEmpty(fileName) == false && fileName.Equals(fileToRead, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
|
||||
foundDir = entry.Name.Substring(0, entry.Name.Length - fileName.Length);
|
||||
fileFound = true;
|
||||
using (var reader = new StreamReader(stream))
|
||||
using (var entryStream = entry.Open())
|
||||
using (var reader = new StreamReader(entryStream))
|
||||
{
|
||||
retVal = reader.ReadToEnd();
|
||||
return false;
|
||||
@@ -77,7 +77,7 @@ namespace Umbraco.Core.Packaging
|
||||
var d = sourceDestination.ToDictionary(k => k.Key.ToLower(), v => v.Value);
|
||||
|
||||
|
||||
ReadZipfileEntries(packageFilePath, (entry, stream) =>
|
||||
ReadZipfileEntries(packageFilePath, entry =>
|
||||
{
|
||||
string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower();
|
||||
if (fileName == string.Empty) { return true; }
|
||||
@@ -86,8 +86,9 @@ namespace Umbraco.Core.Packaging
|
||||
if (string.IsNullOrEmpty(fileName) == false && d.TryGetValue(fileName, out destination))
|
||||
{
|
||||
using (var streamWriter = File.Open(destination, FileMode.Create))
|
||||
using (var entryStream = entry.Open())
|
||||
{
|
||||
stream.CopyTo(streamWriter);
|
||||
entryStream.CopyTo(streamWriter);
|
||||
}
|
||||
|
||||
d.Remove(fileName);
|
||||
@@ -106,7 +107,7 @@ namespace Umbraco.Core.Packaging
|
||||
{
|
||||
var retVal = expectedFiles.ToList();
|
||||
|
||||
ReadZipfileEntries(packageFilePath, (zipEntry, stream) =>
|
||||
ReadZipfileEntries(packageFilePath, zipEntry =>
|
||||
{
|
||||
string fileName = Path.GetFileName(zipEntry.Name);
|
||||
|
||||
@@ -125,7 +126,7 @@ namespace Umbraco.Core.Packaging
|
||||
var dictionary = new Dictionary<string, List<string>>();
|
||||
|
||||
|
||||
ReadZipfileEntries(packageFilePath, (entry, stream) =>
|
||||
ReadZipfileEntries(packageFilePath, entry =>
|
||||
{
|
||||
string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower();
|
||||
|
||||
@@ -148,53 +149,42 @@ namespace Umbraco.Core.Packaging
|
||||
{
|
||||
CheckPackageExists(packageFilePath);
|
||||
|
||||
var files = new HashSet<string>(filesToGet.Select(f => f.ToLower()));
|
||||
var files = new HashSet<string>(filesToGet.Select(f => f.ToLowerInvariant()));
|
||||
|
||||
using (var fs = File.OpenRead(packageFilePath))
|
||||
using (var fs = File.OpenRead(packageFilePath))
|
||||
using (var zipArchive = new ZipArchive(fs))
|
||||
{
|
||||
using (var zipInputStream = new ZipInputStream(fs))
|
||||
foreach (var zipEntry in zipArchive.Entries)
|
||||
{
|
||||
ZipEntry zipEntry;
|
||||
while ((zipEntry = zipInputStream.GetNextEntry()) != null)
|
||||
{
|
||||
|
||||
if (zipEntry.IsDirectory) continue;
|
||||
if (zipEntry.Name.IsNullOrWhiteSpace() && zipEntry.FullName.EndsWith("/")) continue;
|
||||
|
||||
if (files.Contains(zipEntry.Name))
|
||||
if (files.Contains(zipEntry.Name.ToLowerInvariant()))
|
||||
{
|
||||
using (var memStream = new MemoryStream())
|
||||
using (var entryStream = zipEntry.Open())
|
||||
{
|
||||
using (var memStream = new MemoryStream())
|
||||
{
|
||||
zipInputStream.CopyTo(memStream);
|
||||
yield return memStream.ToArray();
|
||||
memStream.Close();
|
||||
}
|
||||
entryStream.CopyTo(memStream);
|
||||
memStream.Close();
|
||||
yield return memStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
zipInputStream.Close();
|
||||
}
|
||||
fs.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadZipfileEntries(string packageFilePath, Func<ZipEntry, ZipInputStream, bool> entryFunc, bool skipsDirectories = true)
|
||||
private void ReadZipfileEntries(string packageFilePath, Func<ZipArchiveEntry, bool> entryFunc, bool skipsDirectories = true)
|
||||
{
|
||||
CheckPackageExists(packageFilePath);
|
||||
|
||||
using (var fs = File.OpenRead(packageFilePath))
|
||||
using (var zipArchive = new ZipArchive(fs))
|
||||
{
|
||||
using (var zipInputStream = new ZipInputStream(fs))
|
||||
foreach (var zipEntry in zipArchive.Entries)
|
||||
{
|
||||
ZipEntry zipEntry;
|
||||
while ((zipEntry = zipInputStream.GetNextEntry()) != null)
|
||||
{
|
||||
if (zipEntry.IsDirectory && skipsDirectories) continue;
|
||||
if (entryFunc(zipEntry, zipInputStream) == false) break;
|
||||
}
|
||||
|
||||
zipInputStream.Close();
|
||||
if (zipEntry.Name.IsNullOrWhiteSpace() && zipEntry.FullName.EndsWith("/") && skipsDirectories)
|
||||
continue;
|
||||
if (entryFunc(zipEntry) == false) break;
|
||||
}
|
||||
fs.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,18 +98,18 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
//NOTE Currently doesn't add Alias (legacy that eventually will go away)
|
||||
var documentDto = new DocumentDto
|
||||
{
|
||||
Newest = true,
|
||||
NodeId = entity.Id,
|
||||
Published = entity.Published,
|
||||
Text = entity.Name,
|
||||
UpdateDate = entity.UpdateDate,
|
||||
WriterUserId = entity.WriterId,
|
||||
VersionId = entity.Version,
|
||||
ExpiresDate = null,
|
||||
ReleaseDate = null,
|
||||
ContentVersionDto = BuildContentVersionDto(entity)
|
||||
};
|
||||
{
|
||||
Newest = true,
|
||||
NodeId = entity.Id,
|
||||
Published = entity.Published,
|
||||
Text = entity.Name,
|
||||
UpdateDate = entity.UpdateDate,
|
||||
WriterUserId = entity.WriterId,
|
||||
VersionId = entity.Version,
|
||||
ExpiresDate = null,
|
||||
ReleaseDate = null,
|
||||
ContentVersionDto = BuildContentVersionDto(entity)
|
||||
};
|
||||
|
||||
if (entity.Template != null && entity.Template.Id > 0)
|
||||
documentDto.TemplateId = entity.Template.Id;
|
||||
|
||||
@@ -5,6 +5,7 @@ using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
|
||||
internal class MacroFactory
|
||||
{
|
||||
public IMacro BuildEntity(MacroDto dto)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
@@ -9,20 +10,17 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
private readonly IMediaType _contentType;
|
||||
private readonly Guid _nodeObjectTypeId;
|
||||
private readonly int _id;
|
||||
private int _primaryKey;
|
||||
|
||||
public MediaFactory(IMediaType contentType, Guid nodeObjectTypeId, int id)
|
||||
public MediaFactory(IMediaType contentType, Guid nodeObjectTypeId)
|
||||
{
|
||||
_contentType = contentType;
|
||||
_nodeObjectTypeId = nodeObjectTypeId;
|
||||
_id = id;
|
||||
}
|
||||
|
||||
public MediaFactory(Guid nodeObjectTypeId, int id)
|
||||
public MediaFactory(Guid nodeObjectTypeId)
|
||||
{
|
||||
_nodeObjectTypeId = nodeObjectTypeId;
|
||||
_id = id;
|
||||
}
|
||||
|
||||
#region Implementation of IEntityFactory<IMedia,ContentVersionDto>
|
||||
@@ -64,15 +62,27 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
return BuildEntity(dto, _contentType);
|
||||
}
|
||||
|
||||
public ContentVersionDto BuildDto(IMedia entity)
|
||||
public MediaDto BuildDto(IMedia entity)
|
||||
{
|
||||
var dto = new ContentVersionDto
|
||||
{
|
||||
NodeId = entity.Id,
|
||||
VersionDate = entity.UpdateDate,
|
||||
VersionId = entity.Version,
|
||||
ContentDto = BuildContentDto(entity)
|
||||
};
|
||||
var versionDto = new ContentVersionDto
|
||||
{
|
||||
NodeId = entity.Id,
|
||||
VersionDate = entity.UpdateDate,
|
||||
VersionId = entity.Version,
|
||||
ContentDto = BuildContentDto(entity)
|
||||
};
|
||||
|
||||
//Extract the media path for storage
|
||||
string mediaPath;
|
||||
TryMatch(entity.GetValue<string>("umbracoFile"), out mediaPath);
|
||||
|
||||
var dto = new MediaDto()
|
||||
{
|
||||
NodeId = entity.Id,
|
||||
ContentVersionDto = versionDto,
|
||||
MediaPath = mediaPath,
|
||||
VersionId = entity.Version
|
||||
};
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -119,5 +129,30 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
return nodeDto;
|
||||
}
|
||||
|
||||
private static readonly Regex MediaPathPattern = new Regex(@"(/media/.+?)(?:['""]|$)", RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// Try getting a media path out of the string being stored for media
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="mediaPath"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool TryMatch(string text, out string mediaPath)
|
||||
{
|
||||
mediaPath = null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return false;
|
||||
|
||||
var match = MediaPathPattern.Match(text);
|
||||
if (match.Success == false || match.Groups.Count != 2)
|
||||
return false;
|
||||
|
||||
|
||||
var url = match.Groups[1].Value;
|
||||
mediaPath = url;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
user.Avatar = dto.Avatar;
|
||||
user.EmailConfirmedDate = dto.EmailConfirmedDate;
|
||||
user.InvitedDate = dto.InvitedDate;
|
||||
user.TourData = dto.TourData;
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
// http://issues.umbraco.org/issue/U4-1946
|
||||
@@ -69,7 +70,8 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
UpdateDate = entity.UpdateDate,
|
||||
Avatar = entity.Avatar,
|
||||
EmailConfirmedDate = entity.EmailConfirmedDate,
|
||||
InvitedDate = entity.InvitedDate
|
||||
InvitedDate = entity.InvitedDate,
|
||||
TourData = entity.TourData
|
||||
};
|
||||
|
||||
foreach (var startNodeId in entity.StartContentIds)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Mappers
|
||||
{
|
||||
[MapperFor(typeof(AuditItem))]
|
||||
[MapperFor(typeof(IAuditItem))]
|
||||
public sealed class AuditMapper : BaseMapper
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance = new ConcurrentDictionary<string, DtoMapModel>();
|
||||
|
||||
public AuditMapper(ISqlSyntaxProvider sqlSyntax) : base(sqlSyntax)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it
|
||||
// otherwise that would fail because there is no public constructor.
|
||||
public AuditMapper()
|
||||
{
|
||||
BuildMap();
|
||||
}
|
||||
|
||||
#region Overrides of BaseMapper
|
||||
|
||||
internal override ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache
|
||||
{
|
||||
get { return PropertyInfoCacheInstance; }
|
||||
}
|
||||
|
||||
internal override void BuildMap()
|
||||
{
|
||||
if (PropertyInfoCache.IsEmpty)
|
||||
{
|
||||
CacheMap<AuditItem, LogDto>(src => src.Id, dto => dto.NodeId);
|
||||
CacheMap<AuditItem, LogDto>(src => src.CreateDate, dto => dto.Datestamp);
|
||||
CacheMap<AuditItem, LogDto>(src => src.UserId, dto => dto.UserId);
|
||||
CacheMap<AuditItem, LogDto>(src => src.AuditType, dto => dto.Header);
|
||||
CacheMap<AuditItem, LogDto>(src => src.Comment, dto => dto.Comment);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -43,27 +43,28 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
{3, typeof (ContentDto)},
|
||||
{4, typeof (ContentVersionDto)},
|
||||
{5, typeof (DocumentDto)},
|
||||
{6, typeof (MediaDto)},
|
||||
|
||||
{6, typeof (ContentTypeTemplateDto)},
|
||||
{7, typeof (DataTypeDto)},
|
||||
{8, typeof (DataTypePreValueDto)},
|
||||
{9, typeof (DictionaryDto)},
|
||||
{10, typeof (LanguageDto)},
|
||||
{11, typeof (LanguageTextDto)},
|
||||
{12, typeof (DomainDto)},
|
||||
{13, typeof (LogDto)},
|
||||
{14, typeof (MacroDto)},
|
||||
{15, typeof (MacroPropertyDto)},
|
||||
{16, typeof (MemberTypeDto)},
|
||||
{17, typeof (MemberDto)},
|
||||
{18, typeof (Member2MemberGroupDto)},
|
||||
{19, typeof (ContentXmlDto)},
|
||||
{20, typeof (PreviewXmlDto)},
|
||||
{21, typeof (PropertyTypeGroupDto)},
|
||||
{22, typeof (PropertyTypeDto)},
|
||||
{23, typeof (PropertyDataDto)},
|
||||
{24, typeof (RelationTypeDto)},
|
||||
{25, typeof (RelationDto)},
|
||||
{7, typeof (ContentTypeTemplateDto)},
|
||||
{8, typeof (DataTypeDto)},
|
||||
{9, typeof (DataTypePreValueDto)},
|
||||
{10, typeof (DictionaryDto)},
|
||||
{11, typeof (LanguageDto)},
|
||||
{12, typeof (LanguageTextDto)},
|
||||
{13, typeof (DomainDto)},
|
||||
{14, typeof (LogDto)},
|
||||
{15, typeof (MacroDto)},
|
||||
{16, typeof (MacroPropertyDto)},
|
||||
{17, typeof (MemberTypeDto)},
|
||||
{18, typeof (MemberDto)},
|
||||
{19, typeof (Member2MemberGroupDto)},
|
||||
{20, typeof (ContentXmlDto)},
|
||||
{21, typeof (PreviewXmlDto)},
|
||||
{22, typeof (PropertyTypeGroupDto)},
|
||||
{23, typeof (PropertyTypeDto)},
|
||||
{24, typeof (PropertyDataDto)},
|
||||
{25, typeof (RelationTypeDto)},
|
||||
{26, typeof (RelationDto)},
|
||||
|
||||
{28, typeof (TagDto)},
|
||||
{29, typeof (TagRelationshipDto)},
|
||||
@@ -91,6 +92,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
{52, typeof (UserGroup2NodePermissionDto) },
|
||||
{53, typeof (UserGroup2AppDto) },
|
||||
{54, typeof (UserStartNodeDto) },
|
||||
{55, typeof (UserLoginDto)}
|
||||
};
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -148,6 +148,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
return new Version(7, 6, 0);
|
||||
}
|
||||
|
||||
//if the error is for cmsMedia it must be the previous version to 7.8 since that is when it is added
|
||||
if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("cmsMedia"))))
|
||||
{
|
||||
return new Version(7, 7, 0);
|
||||
}
|
||||
|
||||
return UmbracoVersion.Current;
|
||||
}
|
||||
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.Factories;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
|
||||
{
|
||||
[Migration("7.8.0", 1, Constants.System.UmbracoMigrationName)]
|
||||
public class AddCmsMediaTable : MigrationBase
|
||||
{
|
||||
public AddCmsMediaTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
|
||||
|
||||
if (tables.InvariantContains("cmsMedia") == false)
|
||||
{
|
||||
Create.Table<MediaDto>();
|
||||
|
||||
MigrateMediaPaths();
|
||||
}
|
||||
}
|
||||
|
||||
private void MigrateMediaPaths()
|
||||
{
|
||||
Execute.Code(database =>
|
||||
{
|
||||
//Due to how much data there can be and that this query will result in executing an Index Scan on the cmsPropertyData PK
|
||||
//which will literally iterate over every row in there (assume there's 1,000,000 and you can see why this will perform
|
||||
//horribly) ultimately we need to fix this: http://issues.umbraco.org/issue/U4-10286
|
||||
//so in the meantime I've found an ugly query to work around this Index Scan issue which is to use a sub-query of Ids.
|
||||
//Doing this results in the same Index Scan taking place but it only visits the explicit rows that are provided, not every row
|
||||
//in the cmsPropertyData table. This results in a tremendously faster query when there is tons of data.
|
||||
|
||||
//The will return any media property in nvarchar or ntext that is an umbracoFile that is not null.
|
||||
//This will give us the least amount of data returned to work with.
|
||||
var sql = @"SELECT cmsPropertyData.dataNvarchar, cmsPropertyData.dataNtext, umbracoNode.id, cmsContentVersion.VersionId
|
||||
FROM cmsPropertyData
|
||||
INNER JOIN cmsPropertyType ON cmsPropertyType.id = cmsPropertyData.propertytypeid
|
||||
INNER JOIN umbracoNode ON umbracoNode.id = cmsPropertyData.contentNodeId
|
||||
INNER JOIN cmsContentVersion ON cmsContentVersion.ContentId = umbracoNode.id
|
||||
WHERE cmsPropertyType.Alias = (@alias)
|
||||
AND umbracoNode.id IN (SELECT umbracoNode.id
|
||||
FROM umbracoNode
|
||||
INNER JOIN cmsContent ON cmsContent.nodeId = umbracoNode.id
|
||||
INNER JOIN cmsContentType ON cmsContentType.nodeId = cmsContent.contentType
|
||||
INNER JOIN cmsPropertyType ON cmsPropertyType.contentTypeId = cmsContentType.nodeId
|
||||
WHERE cmsPropertyType.Alias = (@alias) AND umbracoNode.nodeObjectType = (@nodeObjectType))
|
||||
AND (cmsPropertyData.dataNvarchar IS NOT NULL OR cmsPropertyData.dataNtext IS NOT NULL)";
|
||||
|
||||
var paths = new List<MediaDto>();
|
||||
|
||||
//using QUERY = a db cursor, we won't load this all into memory first, just row by row
|
||||
foreach (var row in database.Query<dynamic>(sql, new {alias = "umbracoFile", nodeObjectType = Constants.ObjectTypes.Media}))
|
||||
{
|
||||
var id = (int) row.id;
|
||||
var versionId = (Guid)row.VersionId;
|
||||
|
||||
string mediaPath = null;
|
||||
|
||||
//if there's values in dataNvarchar then ensure there's a media path match and extract it
|
||||
if (row.dataNvarchar != null && MediaFactory.TryMatch((string) row.dataNvarchar, out mediaPath))
|
||||
{
|
||||
paths.Add(new MediaDto
|
||||
{
|
||||
MediaPath = mediaPath,
|
||||
NodeId = id,
|
||||
VersionId = versionId
|
||||
});
|
||||
}
|
||||
//if there's values in dataNtext then ensure there's a media path match and extract it
|
||||
else if (row.dataNtext != null && MediaFactory.TryMatch((string) row.dataNtext, out mediaPath))
|
||||
{
|
||||
paths.Add(new MediaDto
|
||||
{
|
||||
MediaPath = mediaPath,
|
||||
NodeId = id,
|
||||
VersionId = versionId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//now we need to insert paths for each item
|
||||
database.BulkInsertRecords(paths, SqlSyntax);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
|
||||
{
|
||||
[Migration("7.8.0", 0, Constants.System.UmbracoMigrationName)]
|
||||
public class AddIndexToPropertyTypeAliasColumn : MigrationBase
|
||||
{
|
||||
public AddIndexToPropertyTypeAliasColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{ }
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
Execute.Code(database =>
|
||||
{
|
||||
var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database);
|
||||
|
||||
//make sure it doesn't already exist
|
||||
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsPropertyTypeAlias")) == false)
|
||||
{
|
||||
var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger);
|
||||
|
||||
//we can apply the index
|
||||
localContext.Create.Index("IX_cmsPropertyTypeAlias").OnTable("cmsPropertyType")
|
||||
.OnColumn("Alias")
|
||||
.Ascending()
|
||||
.WithOptions()
|
||||
.NonClustered();
|
||||
|
||||
return localContext.GetSql();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
Delete.Index("IX_cmsPropertyTypeAlias").OnTable("cmsPropertyType");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
|
||||
{
|
||||
[Migration("7.8.0", 2, Constants.System.UmbracoMigrationName)]
|
||||
public class AddInstructionCountColumn : MigrationBase
|
||||
{
|
||||
public AddInstructionCountColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
//Don't exeucte if the column is already there
|
||||
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
|
||||
|
||||
if (columns.Any(x => x.TableName.InvariantEquals("umbracoCacheInstruction") && x.ColumnName.InvariantEquals("instructionCount")) == false)
|
||||
Create.Column("instructionCount")
|
||||
.OnTable("umbracoCacheInstruction")
|
||||
.AsInt32()
|
||||
.WithDefaultValue(1)
|
||||
.NotNullable();
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
|
||||
{
|
||||
[Migration("7.8.0", 1, Constants.System.UmbracoMigrationName)]
|
||||
public class AddTourDataUserColumn : MigrationBase
|
||||
{
|
||||
public AddTourDataUserColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
//Don't exeucte if the column is already there
|
||||
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
|
||||
|
||||
if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("tourData")) == false)
|
||||
{
|
||||
var textType = SqlSyntax.GetSpecialDbType(SpecialDbTypes.NTEXT);
|
||||
Create.Column("tourData").OnTable("umbracoUser").AsCustom(textType).Nullable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
|
||||
{
|
||||
[Migration("7.8.0", 4, Constants.System.UmbracoMigrationName)]
|
||||
public class AddUserLoginTable : MigrationBase
|
||||
{
|
||||
public AddUserLoginTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
|
||||
|
||||
if (tables.InvariantContains("umbracoUserLogin") == false)
|
||||
{
|
||||
Create.Table<UserLoginDto>();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using Umbraco.Core.Persistence.FaultHandling;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using MySql.Data.MySqlClient;
|
||||
using StackExchange.Profiling.Data;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
|
||||
@@ -38,8 +38,12 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
_tableDefinition = DefinitionFactory.GetTableDefinition(sqlSyntaxProvider, pd.type);
|
||||
if (_tableDefinition == null) throw new InvalidOperationException("No table definition found for type " + pd.type);
|
||||
|
||||
_readerColumns = pd.Columns.Select(x => x.Value).ToArray();
|
||||
|
||||
//Only return real columns, do not include columns that are result columns
|
||||
_readerColumns = pd.Columns
|
||||
.Where(x => x.Value.ResultColumn == false)
|
||||
.Select(x => x.Value)
|
||||
.ToArray();
|
||||
_sqlSyntaxProvider = sqlSyntaxProvider;
|
||||
_enumerator = dataSource.GetEnumerator();
|
||||
_columnDefinitions = _tableDefinition.Columns.ToArray();
|
||||
|
||||
@@ -1,29 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
internal class AuditRepository : PetaPocoRepositoryBase<int, AuditItem>, IAuditRepository
|
||||
internal class AuditRepository : PetaPocoRepositoryBase<int, IAuditItem>, IAuditRepository
|
||||
{
|
||||
public AuditRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
|
||||
: base(work, cache, logger, sqlSyntax)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void PersistNewItem(AuditItem entity)
|
||||
/// <summary>
|
||||
/// Return the audit items as paged result
|
||||
/// </summary>
|
||||
/// <param name="query">
|
||||
/// The query coming from the service
|
||||
/// </param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <param name="totalRecords"></param>
|
||||
/// <param name="orderDirection"></param>
|
||||
/// <param name="auditTypeFilter">
|
||||
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
|
||||
/// so we need to do that here
|
||||
/// </param>
|
||||
/// <param name="customFilter">
|
||||
/// A user supplied custom filter
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IAuditItem> GetPagedResultsByQuery(IQuery<IAuditItem> query, long pageIndex, int pageSize,
|
||||
out long totalRecords, Direction orderDirection,
|
||||
AuditType[] auditTypeFilter,
|
||||
IQuery<IAuditItem> customFilter)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (auditTypeFilter == null) auditTypeFilter = new AuditType[0];
|
||||
|
||||
var sql = GetBaseQuery(false);
|
||||
|
||||
if (query == null) query = new Query<IAuditItem>();
|
||||
var translatorIds = new SqlTranslator<IAuditItem>(sql, query);
|
||||
var translatedQuery = translatorIds.Translate();
|
||||
|
||||
var customFilterWheres = customFilter != null ? customFilter.GetWhereClauses().ToArray() : null;
|
||||
var hasCustomFilter = customFilterWheres != null && customFilterWheres.Length > 0;
|
||||
if (hasCustomFilter)
|
||||
{
|
||||
var filterSql = new Sql();
|
||||
var first = true;
|
||||
foreach (var filterClaus in customFilterWheres)
|
||||
{
|
||||
if (first == false)
|
||||
{
|
||||
filterSql.Append(" AND ");
|
||||
}
|
||||
filterSql.Append(string.Format("({0})", filterClaus.Item1), filterClaus.Item2);
|
||||
first = false;
|
||||
}
|
||||
|
||||
translatedQuery = GetFilteredSqlForPagedResults(translatedQuery, filterSql);
|
||||
}
|
||||
|
||||
if (auditTypeFilter.Length > 0)
|
||||
{
|
||||
var filterSql = new Sql();
|
||||
var first = true;
|
||||
foreach (var filterClaus in auditTypeFilter)
|
||||
{
|
||||
if (first == false || hasCustomFilter)
|
||||
{
|
||||
filterSql.Append(" AND ");
|
||||
}
|
||||
filterSql.Append("(logHeader = @logHeader)", new {logHeader = filterClaus.ToString() });
|
||||
first = false;
|
||||
}
|
||||
|
||||
translatedQuery = GetFilteredSqlForPagedResults(translatedQuery, filterSql);
|
||||
}
|
||||
|
||||
if (orderDirection == Direction.Descending)
|
||||
translatedQuery.OrderByDescending("Datestamp");
|
||||
else
|
||||
translatedQuery.OrderBy("Datestamp");
|
||||
|
||||
// Get page of results and total count
|
||||
var pagedResult = Database.Page<LogDto>(pageIndex + 1, pageSize, translatedQuery);
|
||||
totalRecords = pagedResult.TotalItems;
|
||||
|
||||
var pages = pagedResult.Items.Select(
|
||||
dto => new AuditItem(dto.Id, dto.Comment, Enum<AuditType>.Parse(dto.Header), dto.UserId)).ToArray();
|
||||
|
||||
//Mapping the DateStamp
|
||||
for (int i = 0; i < pages.Length; i++)
|
||||
{
|
||||
pages[i].CreateDate = pagedResult.Items[i].Datestamp;
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
|
||||
#region Not Implemented - not needed
|
||||
|
||||
protected override void PersistUpdatedItem(AuditItem entity)
|
||||
protected override void PersistUpdatedItem(IAuditItem entity)
|
||||
{
|
||||
Database.Insert(new LogDto
|
||||
{
|
||||
@@ -35,26 +119,40 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
});
|
||||
}
|
||||
|
||||
protected override AuditItem PerformGet(int id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IEnumerable<AuditItem> PerformGetAll(params int[] ids)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IEnumerable<AuditItem> PerformGetByQuery(IQuery<AuditItem> query)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Sql GetBaseQuery(bool isCount)
|
||||
{
|
||||
var sql = new Sql()
|
||||
.Select(isCount ? "COUNT(*)" : "umbracoLog.id, umbracoLog.userId, umbracoLog.NodeId, umbracoLog.Datestamp, umbracoLog.logHeader, umbracoLog.logComment, umbracoUser.userName, umbracoUser.avatar as userAvatar")
|
||||
.From<LogDto>(SqlSyntax);
|
||||
if (isCount == false)
|
||||
{
|
||||
sql = sql.LeftJoin<UserDto>(SqlSyntax).On<UserDto, LogDto>(SqlSyntax, dto => dto.Id, dto => dto.UserId);
|
||||
}
|
||||
return sql;
|
||||
}
|
||||
|
||||
#region Not Implemented - not needed currently
|
||||
|
||||
protected override void PersistNewItem(IAuditItem entity)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IAuditItem PerformGet(int id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IEnumerable<IAuditItem> PerformGetAll(params int[] ids)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IEnumerable<IAuditItem> PerformGetByQuery(IQuery<IAuditItem> query)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override string GetBaseWhereClause()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@@ -71,6 +169,33 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
#endregion
|
||||
|
||||
private Sql GetFilteredSqlForPagedResults(Sql sql, Sql filterSql)
|
||||
{
|
||||
Sql filteredSql;
|
||||
|
||||
// Apply filter
|
||||
if (filterSql != null)
|
||||
{
|
||||
var sqlFilter = " WHERE " + filterSql.SQL.TrimStart("AND ");
|
||||
|
||||
//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(filterSql.Arguments).ToArray();
|
||||
filteredSql = new Sql(
|
||||
string.Format("{0} {1}", filteredSql.SQL, sqlFilter),
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
.InnerJoin<ContentDto>(SqlSyntax)
|
||||
.On<ContentVersionDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<NodeDto>(SqlSyntax)
|
||||
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId);
|
||||
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<ContentTypeDto>()
|
||||
.On<ContentTypeDto, ContentDto>(left => left.NodeId, right => right.ContentTypeId);
|
||||
//TODO: IF we want to enable querying on content type information this will need to be joined
|
||||
//.InnerJoin<ContentTypeDto>(SqlSyntax)
|
||||
//.On<ContentDto, ContentTypeDto>(SqlSyntax, left => left.ContentTypeId, right => right.NodeId, SqlSyntax);
|
||||
@@ -833,11 +835,21 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
|
||||
|
||||
}
|
||||
|
||||
public int CountPublished()
|
||||
{
|
||||
var sql = GetBaseQuery(true).Where<NodeDto>(x => x.Trashed == false)
|
||||
.Where<DocumentDto>(x => x.Published == true);
|
||||
return Database.ExecuteScalar<int>(sql);
|
||||
public int CountPublished(string contentTypeAlias = null)
|
||||
{
|
||||
if (contentTypeAlias.IsNullOrWhiteSpace())
|
||||
{
|
||||
var sql = GetBaseQuery(true).Where<NodeDto>(x => x.Trashed == false)
|
||||
.Where<DocumentDto>(x => x.Published == true);
|
||||
return Database.ExecuteScalar<int>(sql);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sql = GetBaseQuery(true).Where<NodeDto>(x => x.Trashed == false)
|
||||
.Where<DocumentDto>(x => x.Published == true)
|
||||
.Where<ContentTypeDto>(x => x.Alias == contentTypeAlias);
|
||||
return Database.ExecuteScalar<int>(sql);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReplaceContentPermissions(EntityPermissionSet permissionSet)
|
||||
@@ -889,7 +901,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
|
||||
public void AddOrUpdatePermissions(ContentPermissionSet permission)
|
||||
{
|
||||
_permissionRepository.AddOrUpdate(permission);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to remove the content xml for a content item
|
||||
@@ -1063,8 +1075,8 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
protected override string GetDatabaseFieldNameForOrderBy(string orderBy)
|
||||
{
|
||||
//Some custom ones
|
||||
|
||||
@@ -1,11 +1,37 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Auditing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
public interface IAuditRepository : IRepository<int, AuditItem>
|
||||
public interface IAuditRepository : IRepository<int, IAuditItem>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Return the audit items as paged result
|
||||
/// </summary>
|
||||
/// <param name="query">
|
||||
/// The query coming from the service
|
||||
/// </param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <param name="totalRecords"></param>
|
||||
/// <param name="orderDirection"></param>
|
||||
/// <param name="auditTypeFilter">
|
||||
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
|
||||
/// so we need to do that here
|
||||
/// </param>
|
||||
/// <param name="customFilter">
|
||||
/// A user supplied custom filter
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IAuditItem> GetPagedResultsByQuery(
|
||||
IQuery<IAuditItem> query,
|
||||
long pageIndex, int pageSize, out long totalRecords,
|
||||
Direction orderDirection,
|
||||
AuditType[] auditTypeFilter,
|
||||
IQuery<IAuditItem> customFilter);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <remarks>
|
||||
/// We require this on the repo because the IQuery{IContent} cannot supply the 'newest' parameter
|
||||
/// </remarks>
|
||||
int CountPublished();
|
||||
int CountPublished(string contentTypeAlias = null);
|
||||
|
||||
/// <summary>
|
||||
/// Used to bulk update the permissions set for a content item. This will replace all permissions
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
public interface IMediaRepository : IRepositoryVersionable<int, IMedia>, IRecycleBinRepository<IMedia>, IReadRepository<Guid, IMedia>, IDeleteMediaFilesRepository
|
||||
{
|
||||
IMedia GetMediaByPath(string mediaPath);
|
||||
|
||||
/// <summary>
|
||||
/// Used to add/update published xml for the media item
|
||||
/// </summary>
|
||||
|
||||
@@ -91,7 +91,12 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
IProfile GetProfile(string username);
|
||||
IProfile GetProfile(int id);
|
||||
IDictionary<UserState, int> GetUserStates();
|
||||
|
||||
IDictionary<UserState, int> GetUserStates();
|
||||
|
||||
Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true);
|
||||
bool ValidateLoginSession(int userId, Guid sessionId);
|
||||
int ClearLoginSessions(int userId);
|
||||
int ClearLoginSessions(TimeSpan timespan);
|
||||
void ClearLoginSession(Guid sessionId);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -87,7 +87,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
#region Overrides of PetaPocoRepositoryBase<int,IMedia>
|
||||
|
||||
protected override Sql GetBaseQuery(BaseQueryType queryType)
|
||||
private Sql GetBaseQuery(BaseQueryType queryType, bool includeFilePaths)
|
||||
{
|
||||
var sql = new Sql();
|
||||
sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsContentVersion.contentId" : "*"))
|
||||
@@ -95,14 +95,26 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
.InnerJoin<ContentDto>(SqlSyntax)
|
||||
.On<ContentVersionDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<NodeDto>(SqlSyntax)
|
||||
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId, SqlSyntax)
|
||||
//TODO: IF we want to enable querying on content type information this will need to be joined
|
||||
//.InnerJoin<ContentTypeDto>(SqlSyntax)
|
||||
//.On<ContentDto, ContentTypeDto>(SqlSyntax, left => left.ContentTypeId, right => right.NodeId, SqlSyntax);
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
|
||||
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId);
|
||||
|
||||
if (includeFilePaths)
|
||||
{
|
||||
sql.InnerJoin<MediaDto>(SqlSyntax)
|
||||
.On<MediaDto, ContentVersionDto>(SqlSyntax, left => left.VersionId, right => right.VersionId);
|
||||
}
|
||||
|
||||
//TODO: IF we want to enable querying on content type information this will need to be joined
|
||||
//.InnerJoin<ContentTypeDto>(SqlSyntax)
|
||||
//.On<ContentDto, ContentTypeDto>(SqlSyntax, left => left.ContentTypeId, right => right.NodeId, SqlSyntax);
|
||||
sql.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
|
||||
return sql;
|
||||
}
|
||||
|
||||
protected override Sql GetBaseQuery(BaseQueryType queryType)
|
||||
{
|
||||
return GetBaseQuery(queryType, false);
|
||||
}
|
||||
|
||||
protected override Sql GetBaseQuery(bool isCount)
|
||||
{
|
||||
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle);
|
||||
@@ -128,6 +140,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
"DELETE FROM cmsDocument WHERE nodeId = @Id",
|
||||
"DELETE FROM cmsPropertyData WHERE contentNodeId = @Id",
|
||||
"DELETE FROM cmsPreviewXml WHERE nodeId = @Id",
|
||||
"DELETE FROM cmsMedia WHERE nodeId = @Id",
|
||||
"DELETE FROM cmsContentVersion WHERE ContentId = @Id",
|
||||
"DELETE FROM cmsContentXml WHERE nodeId = @Id",
|
||||
"DELETE FROM cmsContent WHERE nodeId = @Id",
|
||||
@@ -367,7 +380,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//Ensure that strings don't contain characters that are invalid in XML
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
var factory = new MediaFactory(NodeObjectTypeId, entity.Id);
|
||||
var factory = new MediaFactory(NodeObjectTypeId);
|
||||
var dto = factory.BuildDto(entity);
|
||||
|
||||
//NOTE Should the logic below have some kind of fallback for empty parent ids ?
|
||||
@@ -380,7 +393,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
var sortOrder = maxSortOrder + 1;
|
||||
|
||||
//Create the (base) node data - umbracoNode
|
||||
var nodeDto = dto.ContentDto.NodeDto;
|
||||
var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto;
|
||||
nodeDto.Path = parent.Path;
|
||||
nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture));
|
||||
nodeDto.SortOrder = sortOrder;
|
||||
@@ -415,15 +428,21 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
entity.Level = level;
|
||||
|
||||
//Create the Content specific data - cmsContent
|
||||
var contentDto = dto.ContentDto;
|
||||
var contentDto = dto.ContentVersionDto.ContentDto;
|
||||
contentDto.NodeId = nodeDto.NodeId;
|
||||
Database.Insert(contentDto);
|
||||
|
||||
//Create the first version - cmsContentVersion
|
||||
//Assumes a new Version guid and Version date (modified date) has been set
|
||||
var contentVersionDto = dto.ContentVersionDto;
|
||||
contentVersionDto.NodeId = nodeDto.NodeId;
|
||||
Database.Insert(contentVersionDto);
|
||||
|
||||
//Create the Media specific data for this version - cmsMedia
|
||||
//Assumes a new Version guid has been generated
|
||||
dto.NodeId = nodeDto.NodeId;
|
||||
Database.Insert(dto);
|
||||
|
||||
|
||||
//Create the PropertyData for this version - cmsPropertyData
|
||||
var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id);
|
||||
var propertyDataDtos = propertyFactory.BuildDto(entity.Properties);
|
||||
@@ -471,14 +490,14 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
entity.SortOrder = maxSortOrder + 1;
|
||||
}
|
||||
|
||||
var factory = new MediaFactory(NodeObjectTypeId, entity.Id);
|
||||
var factory = new MediaFactory(NodeObjectTypeId);
|
||||
//Look up Content entry to get Primary for updating the DTO
|
||||
var contentDto = Database.SingleOrDefault<ContentDto>("WHERE nodeId = @Id", new { Id = entity.Id });
|
||||
var contentDto = Database.First<ContentDto>("WHERE nodeId = @Id", new { Id = entity.Id });
|
||||
factory.SetPrimaryKey(contentDto.PrimaryKey);
|
||||
var dto = factory.BuildDto(entity);
|
||||
|
||||
//Updates the (base) node data - umbracoNode
|
||||
var nodeDto = dto.ContentDto.NodeDto;
|
||||
var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto;
|
||||
nodeDto.ValidatePathWithException();
|
||||
var o = Database.Update(nodeDto);
|
||||
|
||||
@@ -486,15 +505,18 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
if (contentDto.ContentTypeId != entity.ContentTypeId)
|
||||
{
|
||||
//Create the Content specific data - cmsContent
|
||||
var newContentDto = dto.ContentDto;
|
||||
var newContentDto = dto.ContentVersionDto.ContentDto;
|
||||
Database.Update(newContentDto);
|
||||
}
|
||||
|
||||
//In order to update the ContentVersion we need to retrieve its primary key id
|
||||
var contentVerDto = Database.SingleOrDefault<ContentVersionDto>("WHERE VersionId = @Version", new { Version = entity.Version });
|
||||
dto.Id = contentVerDto.Id;
|
||||
var contentVerDto = Database.First<ContentVersionDto>("WHERE VersionId = @Version", new { Version = entity.Version });
|
||||
dto.ContentVersionDto.Id = contentVerDto.Id;
|
||||
//Updates the current version - cmsContentVersion
|
||||
//Assumes a Version guid exists and Version date (modified date) has been set/updated
|
||||
Database.Update(dto.ContentVersionDto);
|
||||
|
||||
//now update the media entry
|
||||
Database.Update(dto);
|
||||
|
||||
//Create the PropertyData for this version - cmsPropertyData
|
||||
@@ -639,7 +661,39 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IMedia"/> object from the path stored in the for the media item.
|
||||
/// </summary>
|
||||
/// <param name="mediaPath">Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg)</param>
|
||||
/// <returns><see cref="IMedia"/></returns>
|
||||
public IMedia GetMediaByPath(string mediaPath)
|
||||
{
|
||||
var umbracoFileValue = mediaPath;
|
||||
|
||||
const string pattern = ".*[_][0-9]+[x][0-9]+[.].*";
|
||||
var isResized = Regex.IsMatch(mediaPath, pattern);
|
||||
|
||||
// If the image has been resized we strip the "_403x328" of the original "/media/1024/koala_403x328.jpg" url.
|
||||
if (isResized)
|
||||
{
|
||||
var underscoreIndex = mediaPath.LastIndexOf('_');
|
||||
var dotIndex = mediaPath.LastIndexOf('.');
|
||||
umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex));
|
||||
}
|
||||
|
||||
var sql = GetBaseQuery(BaseQueryType.FullSingle, true);
|
||||
sql.Where<MediaDto>(mediaDto => mediaDto.MediaPath == umbracoFileValue, SqlSyntax);
|
||||
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
|
||||
var dto = Database.Fetch<ContentVersionDto, ContentDto, NodeDto>(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault();
|
||||
if (dto == null)
|
||||
return null;
|
||||
|
||||
var content = CreateMediaFromDto(dto, sql);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets paged media results
|
||||
@@ -703,8 +757,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
if (EnsureUniqueNaming == false)
|
||||
return nodeName;
|
||||
|
||||
var names = Database.Fetch<SimilarNodeName>("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId",
|
||||
new { objectType = NodeObjectTypeId, parentId });
|
||||
var names = Database.Fetch<SimilarNodeName>("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId",
|
||||
new { objectType = NodeObjectTypeId, parentId });
|
||||
|
||||
return SimilarNodeName.GetUniqueName(names, id, nodeName);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
@@ -53,15 +48,16 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
@"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)",
|
||||
WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)",
|
||||
FormatDeleteStatement("umbracoUserStartNode", "startNode"),
|
||||
FormatUpdateStatement("umbracoUserGroup", "startContentId"),
|
||||
FormatUpdateStatement("umbracoUserGroup", "startMediaId"),
|
||||
FormatUpdateStatement("umbracoUserGroup", "startMediaId"),
|
||||
FormatDeleteStatement("umbracoRelation", "parentId"),
|
||||
FormatDeleteStatement("umbracoRelation", "childId"),
|
||||
FormatDeleteStatement("cmsTagRelationship", "nodeId"),
|
||||
FormatDeleteStatement("umbracoDomains", "domainRootStructureID"),
|
||||
FormatDeleteStatement("cmsDocument", "nodeId"),
|
||||
FormatDeleteStatement("cmsMedia", "nodeId"),
|
||||
FormatDeleteStatement("cmsPropertyData", "contentNodeId"),
|
||||
FormatDeleteStatement("cmsPreviewXml", "nodeId"),
|
||||
FormatDeleteStatement("cmsContentVersion", "ContentId"),
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Linq;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Web.Security;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
@@ -187,8 +188,8 @@ UNION
|
||||
SELECT '5CountOfInvited' AS colName, COUNT(id) AS num FROM umbracoUser WHERE lastLoginDate IS NULL AND userDisabled = 1 AND invitedDate IS NOT NULL
|
||||
ORDER BY colName";
|
||||
|
||||
var result = Database.Fetch<dynamic>(sql);
|
||||
|
||||
var result = Database.Fetch<dynamic>(sql);
|
||||
|
||||
return new Dictionary<UserState, int>
|
||||
{
|
||||
{UserState.All, result[0].num},
|
||||
@@ -199,6 +200,83 @@ ORDER BY colName";
|
||||
};
|
||||
}
|
||||
|
||||
public Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true)
|
||||
{
|
||||
//TODO: I know this doesn't follow the normal repository conventions which would require us to crete a UserSessionRepository
|
||||
//and also business logic models for these objects but that's just so overkill for what we are doing
|
||||
//and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore
|
||||
var now = DateTime.UtcNow;
|
||||
var dto = new UserLoginDto
|
||||
{
|
||||
UserId = userId,
|
||||
IpAddress = requestingIpAddress,
|
||||
LoggedInUtc = now,
|
||||
LastValidatedUtc = now,
|
||||
LoggedOutUtc = null,
|
||||
SessionId = Guid.NewGuid()
|
||||
};
|
||||
Database.Insert(dto);
|
||||
|
||||
if (cleanStaleSessions)
|
||||
{
|
||||
ClearLoginSessions(TimeSpan.FromDays(15));
|
||||
}
|
||||
|
||||
return dto.SessionId;
|
||||
}
|
||||
|
||||
public bool ValidateLoginSession(int userId, Guid sessionId)
|
||||
{
|
||||
var found = Database.FirstOrDefault<UserLoginDto>("WHERE sessionId=@sessionId", new {sessionId = sessionId});
|
||||
if (found == null || found.UserId != userId || found.LoggedOutUtc.HasValue)
|
||||
return false;
|
||||
|
||||
//now detect if there's been a timeout
|
||||
if (DateTime.UtcNow - found.LastValidatedUtc > TimeSpan.FromMinutes(GlobalSettings.TimeOutInMinutes))
|
||||
{
|
||||
//timeout detected, update the record
|
||||
ClearLoginSession(sessionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
//update the validate date
|
||||
found.LastValidatedUtc = DateTime.UtcNow;
|
||||
Database.Update(found);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int ClearLoginSessions(int userId)
|
||||
{
|
||||
//TODO: I know this doesn't follow the normal repository conventions which would require us to crete a UserSessionRepository
|
||||
//and also business logic models for these objects but that's just so overkill for what we are doing
|
||||
//and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore
|
||||
var count = Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoUserLogin WHERE userId=@userId", new { userId = userId });
|
||||
Database.Execute("DELETE FROM umbracoUserLogin WHERE userId=@userId", new {userId = userId});
|
||||
return count;
|
||||
}
|
||||
|
||||
public int ClearLoginSessions(TimeSpan timespan)
|
||||
{
|
||||
//TODO: I know this doesn't follow the normal repository conventions which would require us to crete a UserSessionRepository
|
||||
//and also business logic models for these objects but that's just so overkill for what we are doing
|
||||
//and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore
|
||||
|
||||
var fromDate = DateTime.UtcNow - timespan;
|
||||
|
||||
var count = Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoUserLogin WHERE lastValidatedUtc=@fromDate", new { fromDate = fromDate });
|
||||
Database.Execute("DELETE FROM umbracoUserLogin WHERE lastValidatedUtc=@fromDate", new { fromDate = fromDate });
|
||||
return count;
|
||||
}
|
||||
|
||||
public void ClearLoginSession(Guid sessionId)
|
||||
{
|
||||
//TODO: I know this doesn't follow the normal repository conventions which would require us to crete a UserSessionRepository
|
||||
//and also business logic models for these objects but that's just so overkill for what we are doing
|
||||
//and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore
|
||||
Database.Execute("UPDATE umbracoUserLogin SET loggedOutUtc=@now WHERE sessionId=@sessionId",
|
||||
new { now = DateTime.UtcNow, sessionId = sessionId });
|
||||
}
|
||||
|
||||
protected override IEnumerable<IUser> PerformGetAll(params int[] ids)
|
||||
{
|
||||
var sql = GetQueryWithGroups();
|
||||
@@ -401,7 +479,8 @@ ORDER BY colName";
|
||||
{"updateDate", "UpdateDate"},
|
||||
{"avatar", "Avatar"},
|
||||
{"emailConfirmedDate", "EmailConfirmedDate"},
|
||||
{"invitedDate", "InvitedDate"}
|
||||
{"invitedDate", "InvitedDate"},
|
||||
{"tourData", "TourData"}
|
||||
};
|
||||
|
||||
//create list of properties that have changed
|
||||
@@ -834,14 +913,6 @@ ORDER BY colName";
|
||||
{
|
||||
return dtos.Select(UserFactory.BuildEntity);
|
||||
}
|
||||
|
||||
//private IEnumerable<IUserGroup> ConvertFromDtos(IEnumerable<UserGroupDto> dtos)
|
||||
//{
|
||||
// return dtos.Select(dto =>
|
||||
// {
|
||||
// var userGroupFactory = new UserGroupFactory();
|
||||
// return userGroupFactory.BuildEntity(dto);
|
||||
// });
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Web.Compilation;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.IO;
|
||||
@@ -15,6 +16,7 @@ using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Profiling;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using umbraco.interfaces;
|
||||
using Umbraco.Core.Configuration;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace Umbraco.Core
|
||||
@@ -39,7 +41,8 @@ namespace Umbraco.Core
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IRuntimeCacheProvider _runtimeCache;
|
||||
private readonly ProfilingLogger _logger;
|
||||
private readonly string _tempFolder;
|
||||
private readonly Lazy<string> _pluginListFilePath = new Lazy<string>(GetPluginListFilePath);
|
||||
private readonly Lazy<string> _pluginHashFilePath = new Lazy<string>(GetPluginHashFilePath);
|
||||
|
||||
private readonly object _typesLock = new object();
|
||||
private readonly Dictionary<TypeListKey, TypeList> _types = new Dictionary<TypeListKey, TypeList>();
|
||||
@@ -65,14 +68,7 @@ namespace Umbraco.Core
|
||||
_serviceProvider = serviceProvider;
|
||||
_runtimeCache = runtimeCache;
|
||||
_logger = logger;
|
||||
|
||||
// the temp folder where the cache file lives
|
||||
_tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache");
|
||||
if (Directory.Exists(_tempFolder) == false)
|
||||
Directory.CreateDirectory(_tempFolder);
|
||||
|
||||
var pluginListFile = GetPluginListFilePath();
|
||||
|
||||
|
||||
if (detectChanges)
|
||||
{
|
||||
//first check if the cached hash is string.Empty, if it is then we need
|
||||
@@ -84,7 +80,8 @@ namespace Umbraco.Core
|
||||
// if the hash has changed, clear out the persisted list no matter what, this will force
|
||||
// rescanning of all plugin types including lazy ones.
|
||||
// http://issues.umbraco.org/issue/U4-4789
|
||||
File.Delete(pluginListFile);
|
||||
if(File.Exists(_pluginListFilePath.Value))
|
||||
File.Delete(_pluginListFilePath.Value);
|
||||
|
||||
WriteCachePluginsHash();
|
||||
}
|
||||
@@ -94,7 +91,8 @@ namespace Umbraco.Core
|
||||
// if the hash has changed, clear out the persisted list no matter what, this will force
|
||||
// rescanning of all plugin types including lazy ones.
|
||||
// http://issues.umbraco.org/issue/U4-4789
|
||||
File.Delete(pluginListFile);
|
||||
if (File.Exists(_pluginListFilePath.Value))
|
||||
File.Delete(_pluginListFilePath.Value);
|
||||
|
||||
// always set to true if we're not detecting (generally only for testing)
|
||||
RequiresRescanning = true;
|
||||
@@ -187,11 +185,10 @@ namespace Umbraco.Core
|
||||
{
|
||||
if (_cachedAssembliesHash != null)
|
||||
return _cachedAssembliesHash;
|
||||
|
||||
if (File.Exists(_pluginHashFilePath.Value) == false) return string.Empty;
|
||||
|
||||
var filePath = GetPluginHashFilePath();
|
||||
if (File.Exists(filePath) == false) return string.Empty;
|
||||
|
||||
var hash = File.ReadAllText(filePath, Encoding.UTF8);
|
||||
var hash = File.ReadAllText(_pluginHashFilePath.Value, Encoding.UTF8);
|
||||
|
||||
_cachedAssembliesHash = hash;
|
||||
return _cachedAssembliesHash;
|
||||
@@ -210,7 +207,7 @@ namespace Umbraco.Core
|
||||
return _currentAssembliesHash;
|
||||
|
||||
_currentAssembliesHash = GetFileHash(new List<Tuple<FileSystemInfo, bool>>
|
||||
{
|
||||
{
|
||||
// the bin folder and everything in it
|
||||
new Tuple<FileSystemInfo, bool>(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Bin)), false),
|
||||
// the app code folder and everything in it
|
||||
@@ -219,7 +216,7 @@ namespace Umbraco.Core
|
||||
new Tuple<FileSystemInfo, bool>(new FileInfo(IOHelper.MapPath("~/global.asax")), false),
|
||||
// trees.config - use the contents to create the hash since this gets resaved on every app startup!
|
||||
new Tuple<FileSystemInfo, bool>(new FileInfo(IOHelper.MapPath(SystemDirectories.Config + "/trees.config")), true)
|
||||
}, _logger);
|
||||
}, _logger);
|
||||
|
||||
return _currentAssembliesHash;
|
||||
}
|
||||
@@ -229,9 +226,8 @@ namespace Umbraco.Core
|
||||
/// Writes the assembly hash file.
|
||||
/// </summary>
|
||||
private void WriteCachePluginsHash()
|
||||
{
|
||||
var filePath = GetPluginHashFilePath();
|
||||
File.WriteAllText(filePath, CurrentAssembliesHash.ToString(), Encoding.UTF8);
|
||||
{
|
||||
File.WriteAllText(_pluginHashFilePath.Value, CurrentAssembliesHash, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -274,7 +270,7 @@ namespace Umbraco.Core
|
||||
}
|
||||
}
|
||||
}
|
||||
return generator.GenerateHash();
|
||||
return generator.GenerateHash();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,7 +310,7 @@ namespace Umbraco.Core
|
||||
uniqInfos.Add(fileOrFolder.FullName);
|
||||
generator.AddFileSystemItem(fileOrFolder);
|
||||
}
|
||||
return generator.GenerateHash();
|
||||
return generator.GenerateHash();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,12 +343,11 @@ namespace Umbraco.Core
|
||||
{
|
||||
return ReadCache();
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var filePath = GetPluginListFilePath();
|
||||
File.Delete(filePath);
|
||||
File.Delete(_pluginListFilePath.Value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -366,12 +361,11 @@ namespace Umbraco.Core
|
||||
internal Dictionary<Tuple<string, string>, IEnumerable<string>> ReadCache()
|
||||
{
|
||||
var cache = new Dictionary<Tuple<string, string>, IEnumerable<string>>();
|
||||
|
||||
var filePath = GetPluginListFilePath();
|
||||
if (File.Exists(filePath) == false)
|
||||
|
||||
if (File.Exists(_pluginListFilePath.Value) == false)
|
||||
return cache;
|
||||
|
||||
using (var stream = GetFileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, ListFileOpenReadTimeout))
|
||||
using (var stream = GetFileStream(_pluginListFilePath.Value, FileMode.Open, FileAccess.Read, FileShare.Read, ListFileOpenReadTimeout))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
while (true)
|
||||
@@ -414,38 +408,86 @@ namespace Umbraco.Core
|
||||
/// <remarks>Generally only used for resetting cache, for example during the install process.</remarks>
|
||||
public void ClearPluginCache()
|
||||
{
|
||||
var path = GetPluginListFilePath();
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
|
||||
path = GetPluginHashFilePath();
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
if (File.Exists(_pluginListFilePath.Value))
|
||||
File.Delete(_pluginListFilePath.Value);
|
||||
|
||||
if (File.Exists(_pluginHashFilePath.Value))
|
||||
File.Delete(_pluginHashFilePath.Value);
|
||||
|
||||
_runtimeCache.ClearCacheItem(CacheKey);
|
||||
}
|
||||
|
||||
private string GetPluginListFilePath()
|
||||
private static string GetPluginListFilePath()
|
||||
{
|
||||
var filename = "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".list";
|
||||
return Path.Combine(_tempFolder, filename);
|
||||
string pluginListFilePath;
|
||||
switch (GlobalSettings.LocalTempStorageLocation)
|
||||
{
|
||||
case LocalTempStorage.AspNetTemp:
|
||||
pluginListFilePath = Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco-plugins.list");
|
||||
break;
|
||||
case LocalTempStorage.EnvironmentTemp:
|
||||
var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1();
|
||||
var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData",
|
||||
//include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back
|
||||
// to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not
|
||||
// utilizing an old path
|
||||
appDomainHash);
|
||||
pluginListFilePath = Path.Combine(cachePath, "umbraco-plugins.list");
|
||||
break;
|
||||
case LocalTempStorage.Default:
|
||||
default:
|
||||
var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache");
|
||||
pluginListFilePath = Path.Combine(tempFolder, "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".list");
|
||||
break;
|
||||
}
|
||||
|
||||
//ensure the folder exists
|
||||
var folder = Path.GetDirectoryName(pluginListFilePath);
|
||||
if (folder == null)
|
||||
throw new InvalidOperationException("The folder could not be determined for the file " + pluginListFilePath);
|
||||
if (Directory.Exists(folder) == false)
|
||||
Directory.CreateDirectory(folder);
|
||||
|
||||
return pluginListFilePath;
|
||||
}
|
||||
|
||||
private string GetPluginHashFilePath()
|
||||
private static string GetPluginHashFilePath()
|
||||
{
|
||||
var filename = "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".hash";
|
||||
return Path.Combine(_tempFolder, filename);
|
||||
string pluginHashFilePath;
|
||||
switch (GlobalSettings.LocalTempStorageLocation)
|
||||
{
|
||||
case LocalTempStorage.AspNetTemp:
|
||||
pluginHashFilePath = Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco-plugins.hash");
|
||||
break;
|
||||
case LocalTempStorage.EnvironmentTemp:
|
||||
var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1();
|
||||
var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData",
|
||||
//include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back
|
||||
// to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not
|
||||
// utilizing an old path
|
||||
appDomainHash);
|
||||
pluginHashFilePath = Path.Combine(cachePath, "umbraco-plugins.hash");
|
||||
break;
|
||||
case LocalTempStorage.Default:
|
||||
default:
|
||||
var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache");
|
||||
pluginHashFilePath = Path.Combine(tempFolder, "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".hash");
|
||||
break;
|
||||
}
|
||||
|
||||
//ensure the folder exists
|
||||
var folder = Path.GetDirectoryName(pluginHashFilePath);
|
||||
if (folder == null)
|
||||
throw new InvalidOperationException("The folder could not be determined for the file " + pluginHashFilePath);
|
||||
if (Directory.Exists(folder) == false)
|
||||
Directory.CreateDirectory(folder);
|
||||
|
||||
return pluginHashFilePath;
|
||||
}
|
||||
|
||||
internal void WriteCache()
|
||||
{
|
||||
// be absolutely sure
|
||||
if (Directory.Exists(_tempFolder) == false)
|
||||
Directory.CreateDirectory(_tempFolder);
|
||||
|
||||
var filePath = GetPluginListFilePath();
|
||||
|
||||
using (var stream = GetFileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, ListFileOpenWriteTimeout))
|
||||
using (var stream = GetFileStream(_pluginListFilePath.Value, FileMode.Create, FileAccess.Write, FileShare.None, ListFileOpenWriteTimeout))
|
||||
using (var writer = new StreamWriter(stream))
|
||||
{
|
||||
foreach (var typeList in _types.Values)
|
||||
@@ -461,8 +503,7 @@ namespace Umbraco.Core
|
||||
|
||||
internal void UpdateCache()
|
||||
{
|
||||
// note
|
||||
// at the moment we write the cache to disk every time we update it. ideally we defer the writing
|
||||
// TODO: at the moment we write the cache to disk every time we update it. ideally we defer the writing
|
||||
// since all the updates are going to happen in a row when Umbraco starts. that being said, the
|
||||
// file is small enough, so it is not a priority.
|
||||
WriteCache();
|
||||
@@ -475,13 +516,13 @@ namespace Umbraco.Core
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
{
|
||||
return new FileStream(path, fileMode, fileAccess, fileShare);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (--attempts == 0)
|
||||
throw;
|
||||
throw;
|
||||
|
||||
LogHelper.Debug<PluginManager>(string.Format("Attempted to get filestream for file {0} failed, {1} attempts left, pausing for {2} milliseconds", path, attempts, pauseMilliseconds));
|
||||
Thread.Sleep(pauseMilliseconds);
|
||||
@@ -560,7 +601,7 @@ namespace Umbraco.Core
|
||||
if (cache == false || typeof(IDiscoverable).IsAssignableFrom(typeof(T)) == false)
|
||||
{
|
||||
return ResolveTypesInternal(
|
||||
typeof (T), null,
|
||||
typeof(T), null,
|
||||
() => TypeFinder.FindClassesOfType<T>(specificAssemblies ?? AssembliesToScan),
|
||||
cache);
|
||||
}
|
||||
@@ -569,14 +610,14 @@ namespace Umbraco.Core
|
||||
// filter the cached discovered types (and cache the result)
|
||||
|
||||
var discovered = ResolveTypesInternal(
|
||||
typeof (IDiscoverable), null,
|
||||
typeof(IDiscoverable), null,
|
||||
() => TypeFinder.FindClassesOfType<IDiscoverable>(AssembliesToScan),
|
||||
true);
|
||||
|
||||
return ResolveTypesInternal(
|
||||
typeof (T), null,
|
||||
typeof(T), null,
|
||||
() => discovered
|
||||
.Where(x => typeof (T).IsAssignableFrom(x)),
|
||||
.Where(x => typeof(T).IsAssignableFrom(x)),
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -599,7 +640,7 @@ namespace Umbraco.Core
|
||||
if (cache == false || typeof(IDiscoverable).IsAssignableFrom(typeof(T)) == false)
|
||||
{
|
||||
return ResolveTypesInternal(
|
||||
typeof (T), typeof (TAttribute),
|
||||
typeof(T), typeof(TAttribute),
|
||||
() => TypeFinder.FindClassesOfTypeWithAttribute<T, TAttribute>(specificAssemblies ?? AssembliesToScan),
|
||||
cache);
|
||||
}
|
||||
@@ -608,12 +649,12 @@ namespace Umbraco.Core
|
||||
// filter the cached discovered types (and cache the result)
|
||||
|
||||
var discovered = ResolveTypesInternal(
|
||||
typeof (IDiscoverable), null,
|
||||
typeof(IDiscoverable), null,
|
||||
() => TypeFinder.FindClassesOfType<IDiscoverable>(AssembliesToScan),
|
||||
true);
|
||||
|
||||
return ResolveTypesInternal(
|
||||
typeof (T), typeof (TAttribute),
|
||||
typeof(T), typeof(TAttribute),
|
||||
() => discovered
|
||||
.Where(x => typeof(T).IsAssignableFrom(x))
|
||||
.Where(x => x.GetCustomAttributes<TAttribute>(false).Any()),
|
||||
@@ -635,7 +676,7 @@ namespace Umbraco.Core
|
||||
cache &= specificAssemblies == null;
|
||||
|
||||
return ResolveTypesInternal(
|
||||
typeof (object), typeof (TAttribute),
|
||||
typeof(object), typeof(TAttribute),
|
||||
() => TypeFinder.FindClassesWithAttribute<TAttribute>(specificAssemblies ?? AssembliesToScan),
|
||||
cache);
|
||||
}
|
||||
@@ -653,13 +694,13 @@ namespace Umbraco.Core
|
||||
var name = ResolvedName(baseType, attributeType);
|
||||
|
||||
lock (_typesLock)
|
||||
using (_logger.TraceDuration<PluginManager>(
|
||||
"Resolving " + name,
|
||||
"Resolved " + name)) // cannot contain typesFound.Count as it's evaluated before the find
|
||||
{
|
||||
// resolve within a lock & timer
|
||||
return ResolveTypesInternalLocked(baseType, attributeType, finder, cache);
|
||||
}
|
||||
using (_logger.TraceDuration<PluginManager>(
|
||||
"Resolving " + name,
|
||||
"Resolved " + name)) // cannot contain typesFound.Count as it's evaluated before the find
|
||||
{
|
||||
// resolve within a lock & timer
|
||||
return ResolveTypesInternalLocked(baseType, attributeType, finder, cache);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolvedName(Type baseType, Type attributeType)
|
||||
@@ -691,7 +732,7 @@ namespace Umbraco.Core
|
||||
// else proceed,
|
||||
typeList = new TypeList(baseType, attributeType);
|
||||
|
||||
var scan = RequiresRescanning || File.Exists(GetPluginListFilePath()) == false;
|
||||
var scan = RequiresRescanning || File.Exists(_pluginListFilePath.Value) == false;
|
||||
|
||||
if (scan)
|
||||
{
|
||||
@@ -795,7 +836,7 @@ namespace Umbraco.Core
|
||||
|
||||
public TypeListKey(Type baseType, Type attributeType)
|
||||
{
|
||||
BaseType = baseType ?? typeof (object);
|
||||
BaseType = baseType ?? typeof(object);
|
||||
AttributeType = attributeType;
|
||||
}
|
||||
|
||||
@@ -813,7 +854,7 @@ namespace Umbraco.Core
|
||||
|
||||
var hash = 5381;
|
||||
hash = ((hash << 5) + hash) ^ BaseType.GetHashCode();
|
||||
hash = ((hash << 5) + hash) ^ (AttributeType ?? typeof (TypeListKey)).GetHashCode();
|
||||
hash = ((hash << 5) + hash) ^ (AttributeType ?? typeof(TypeListKey)).GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
@@ -876,7 +917,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
// look for IParameterEditor (fast, IDiscoverable) then filter
|
||||
|
||||
var propertyEditor = typeof (PropertyEditor);
|
||||
var propertyEditor = typeof(PropertyEditor);
|
||||
|
||||
return mgr.ResolveTypes<IParameterEditor>()
|
||||
.Where(x => propertyEditor.IsAssignableFrom(x) && x != propertyEditor);
|
||||
@@ -891,8 +932,8 @@ namespace Umbraco.Core
|
||||
/// </remarks>
|
||||
public static IEnumerable<Type> ResolveParameterEditors(this PluginManager mgr)
|
||||
{
|
||||
var propertyEditor = typeof (PropertyEditor);
|
||||
var parameterEditor = typeof (ParameterEditor);
|
||||
var propertyEditor = typeof(PropertyEditor);
|
||||
var parameterEditor = typeof(ParameterEditor);
|
||||
|
||||
return mgr.ResolveTypes<IParameterEditor>()
|
||||
.Where(x => x != propertyEditor && x != parameterEditor);
|
||||
|
||||
@@ -178,29 +178,25 @@ namespace Umbraco.Core.PropertyEditors
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ConvertItemsToJsonIfDetected(IDictionary<string, object> result)
|
||||
protected void ConvertItemsToJsonIfDetected(IDictionary<string, object> result)
|
||||
{
|
||||
//now we're going to try to see if any of the values are JSON, if they are we'll convert them to real JSON objects
|
||||
// so they can be consumed as real json in angular!
|
||||
// convert values that are Json to true Json objects that can be consumed by Angular
|
||||
|
||||
var keys = result.Keys.ToArray();
|
||||
for (var i = 0; i < keys.Length; i++)
|
||||
{
|
||||
if (result[keys[i]] is string)
|
||||
if ((result[keys[i]] is string) == false) continue;
|
||||
|
||||
var asString = result[keys[i]].ToString();
|
||||
if (asString.DetectIsJson() == false) continue;
|
||||
|
||||
try
|
||||
{
|
||||
var asString = result[keys[i]].ToString();
|
||||
if (asString.DetectIsJson())
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.DeserializeObject(asString);
|
||||
result[keys[i]] = json;
|
||||
}
|
||||
catch
|
||||
{
|
||||
//swallow this exception, we thought it was json but it really isn't so continue returning a string
|
||||
}
|
||||
}
|
||||
result[keys[i]] = JsonConvert.DeserializeObject(asString);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// swallow this exception, we thought it was Json but it really isn't so continue returning a string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors.ValueConverters
|
||||
{
|
||||
[DefaultPropertyValueConverter]
|
||||
[PropertyValueType(typeof(string))]
|
||||
[PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)]
|
||||
public class ColorPickerValueConverter : PropertyValueConverterBase
|
||||
public class ColorPickerValueConverter : PropertyValueConverterBase, IPropertyValueConverterMeta
|
||||
{
|
||||
public override bool IsConverter(PublishedPropertyType propertyType)
|
||||
{
|
||||
@@ -18,11 +19,60 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
|
||||
return false;
|
||||
}
|
||||
|
||||
public Type GetPropertyValueType(PublishedPropertyType propertyType)
|
||||
{
|
||||
return UseLabel(propertyType) ? typeof(PickedColor) : typeof(string);
|
||||
}
|
||||
|
||||
public PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType, PropertyCacheValue cacheValue)
|
||||
{
|
||||
return PropertyCacheLevel.Content;
|
||||
}
|
||||
|
||||
private bool UseLabel(PublishedPropertyType propertyType)
|
||||
{
|
||||
var preValues = ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId);
|
||||
PreValue preValue;
|
||||
return preValues.PreValuesAsDictionary.TryGetValue("useLabel", out preValue) && preValue.Value == "1";
|
||||
}
|
||||
|
||||
public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview)
|
||||
{
|
||||
// make sure it's a string
|
||||
return source == null ? string.Empty : source.ToString();
|
||||
var useLabel = UseLabel(propertyType);
|
||||
|
||||
if (source == null) return useLabel ? null : string.Empty;
|
||||
|
||||
var ssource = source.ToString();
|
||||
if (ssource.DetectIsJson())
|
||||
{
|
||||
try
|
||||
{
|
||||
var jo = JsonConvert.DeserializeObject<JObject>(ssource);
|
||||
if (useLabel) return new PickedColor(jo["value"].ToString(), jo["label"].ToString());
|
||||
return jo["value"].ToString();
|
||||
}
|
||||
catch { /* not json finally */ }
|
||||
}
|
||||
|
||||
if (useLabel) return new PickedColor(ssource, ssource);
|
||||
return ssource;
|
||||
}
|
||||
|
||||
public class PickedColor
|
||||
{
|
||||
public PickedColor(string color, string label)
|
||||
{
|
||||
Color = color;
|
||||
Label = label;
|
||||
}
|
||||
|
||||
public string Color { get; private set; }
|
||||
public string Label { get; private set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Color;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
|
||||
var gridConfig = UmbracoConfig.For.GridConfig(
|
||||
ApplicationContext.Current.ProfilingLogger.Logger,
|
||||
ApplicationContext.Current.ApplicationCache.RuntimeCache,
|
||||
new DirectoryInfo(HttpContext.Current.Server.MapPath(SystemDirectories.AppPlugins)),
|
||||
new DirectoryInfo(HttpContext.Current.Server.MapPath(SystemDirectories.Config)),
|
||||
new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)),
|
||||
new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config)),
|
||||
HttpContext.Current.IsDebuggingEnabled);
|
||||
|
||||
var sections = GetArray(obj, "sections");
|
||||
|
||||
@@ -11,12 +11,14 @@ using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Web.Security;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using AutoMapper;
|
||||
using Microsoft.Owin;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Logging;
|
||||
using IUser = Umbraco.Core.Models.Membership.IUser;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
@@ -241,8 +243,11 @@ namespace Umbraco.Core.Security
|
||||
/// <param name="userdata"></param>
|
||||
public static FormsAuthenticationTicket CreateUmbracoAuthTicket(this HttpContextBase http, UserData userdata)
|
||||
{
|
||||
//ONLY used by BasePage.doLogin!
|
||||
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
if (userdata == null) throw new ArgumentNullException("userdata");
|
||||
if (userdata == null) throw new ArgumentNullException("userdata");
|
||||
|
||||
var userDataString = JsonConvert.SerializeObject(userdata);
|
||||
return CreateAuthTicketAndCookie(
|
||||
http,
|
||||
@@ -254,14 +259,7 @@ namespace Umbraco.Core.Security
|
||||
1440,
|
||||
UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName,
|
||||
UmbracoConfig.For.UmbracoSettings().Security.AuthCookieDomain);
|
||||
}
|
||||
|
||||
internal static FormsAuthenticationTicket CreateUmbracoAuthTicket(this HttpContext http, UserData userdata)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
if (userdata == null) throw new ArgumentNullException("userdata");
|
||||
return new HttpContextWrapper(http).CreateUmbracoAuthTicket(userdata);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns the number of seconds the user has until their auth session times out
|
||||
@@ -331,7 +329,23 @@ namespace Umbraco.Core.Security
|
||||
/// <param name="http"></param>
|
||||
/// <param name="cookieName"></param>
|
||||
private static void Logout(this HttpContextBase http, string cookieName)
|
||||
{
|
||||
{
|
||||
//We need to clear the sessionId from the database. This is legacy code to do any logging out and shouldn't really be used at all but in any case
|
||||
//we need to make sure the session is cleared. Due to the legacy nature of this it means we need to use singletons
|
||||
if (http.User != null)
|
||||
{
|
||||
var claimsIdentity = http.User.Identity as ClaimsIdentity;
|
||||
if (claimsIdentity != null)
|
||||
{
|
||||
var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
Guid guidSession;
|
||||
if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out guidSession))
|
||||
{
|
||||
ApplicationContext.Current.Services.UserService.ClearLoginSession(guidSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
//clear the preview cookie and external login
|
||||
var cookies = new[] { cookieName, Constants.Web.PreviewCookieName, Constants.Security.BackOfficeExternalCookieName };
|
||||
|
||||
@@ -2,16 +2,29 @@
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
public class BackOfficeClaimsIdentityFactory<T> : ClaimsIdentityFactory<T, int>
|
||||
where T: BackOfficeIdentityUser
|
||||
{
|
||||
private readonly ApplicationContext _appCtx;
|
||||
|
||||
[Obsolete("Use the overload specifying all dependencies instead")]
|
||||
public BackOfficeClaimsIdentityFactory()
|
||||
:this(ApplicationContext.Current)
|
||||
{
|
||||
}
|
||||
|
||||
public BackOfficeClaimsIdentityFactory(ApplicationContext appCtx)
|
||||
{
|
||||
if (appCtx == null) throw new ArgumentNullException("appCtx");
|
||||
_appCtx = appCtx;
|
||||
|
||||
SecurityStampClaimType = Constants.Security.SessionIdClaimType;
|
||||
UserNameClaimType = ClaimTypes.Name;
|
||||
}
|
||||
@@ -24,9 +37,9 @@ namespace Umbraco.Core.Security
|
||||
public override async Task<ClaimsIdentity> CreateAsync(UserManager<T, int> manager, T user, string authenticationType)
|
||||
{
|
||||
var baseIdentity = await base.CreateAsync(manager, user, authenticationType);
|
||||
|
||||
|
||||
var umbracoIdentity = new UmbracoBackOfficeIdentity(baseIdentity,
|
||||
//set a new session id
|
||||
//NOTE - there is no session id assigned here, this is just creating the identity, a session id will be generated when the cookie is written
|
||||
new UserData
|
||||
{
|
||||
Id = user.Id,
|
||||
@@ -37,15 +50,22 @@ namespace Umbraco.Core.Security
|
||||
Roles = user.Roles.Select(x => x.RoleId).ToArray(),
|
||||
StartContentNodes = user.CalculatedContentStartNodeIds,
|
||||
StartMediaNodes = user.CalculatedMediaStartNodeIds,
|
||||
SessionId = user.SecurityStamp
|
||||
SecurityStamp = user.SecurityStamp
|
||||
});
|
||||
|
||||
return umbracoIdentity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BackOfficeClaimsIdentityFactory : BackOfficeClaimsIdentityFactory<BackOfficeIdentityUser>
|
||||
{
|
||||
{
|
||||
[Obsolete("Use the overload specifying all dependencies instead")]
|
||||
public BackOfficeClaimsIdentityFactory()
|
||||
{
|
||||
}
|
||||
|
||||
public BackOfficeClaimsIdentityFactory(ApplicationContext appCtx) : base(appCtx)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security.Cookies;
|
||||
using Semver;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
public class BackOfficeCookieAuthenticationProvider : CookieAuthenticationProvider
|
||||
{
|
||||
private readonly ApplicationContext _appCtx;
|
||||
|
||||
[Obsolete("Use the ctor specifying all dependencies")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public BackOfficeCookieAuthenticationProvider()
|
||||
: this(ApplicationContext.Current)
|
||||
{
|
||||
}
|
||||
|
||||
public BackOfficeCookieAuthenticationProvider(ApplicationContext appCtx)
|
||||
{
|
||||
if (appCtx == null) throw new ArgumentNullException("appCtx");
|
||||
_appCtx = appCtx;
|
||||
}
|
||||
|
||||
private static readonly SemVersion MinUmbracoVersionSupportingLoginSessions = new SemVersion(7, 8);
|
||||
|
||||
public override void ResponseSignIn(CookieResponseSignInContext context)
|
||||
{
|
||||
var backOfficeIdentity = context.Identity as UmbracoBackOfficeIdentity;
|
||||
if (backOfficeIdentity != null)
|
||||
{
|
||||
//generate a session id and assign it
|
||||
//create a session token - if we are configured and not in an upgrade state then use the db, otherwise just generate one
|
||||
|
||||
//NOTE - special check because when we are upgrading to 7.8 we cannot create a session since the db isn't ready and we'll get exceptions
|
||||
var canAcquireSession = _appCtx.IsUpgrading == false || _appCtx.CurrentVersion() >= MinUmbracoVersionSupportingLoginSessions;
|
||||
|
||||
var session = canAcquireSession
|
||||
? _appCtx.Services.UserService.CreateLoginSession((int)backOfficeIdentity.Id, context.OwinContext.GetCurrentRequestIpAddress())
|
||||
: Guid.NewGuid();
|
||||
|
||||
backOfficeIdentity.UserData.SessionId = session.ToString();
|
||||
}
|
||||
|
||||
base.ResponseSignIn(context);
|
||||
}
|
||||
|
||||
public override void ResponseSignOut(CookieResponseSignOutContext context)
|
||||
{
|
||||
//Clear the user's session on sign out
|
||||
if (context != null && context.OwinContext != null && context.OwinContext.Authentication != null
|
||||
&& context.OwinContext.Authentication.User != null && context.OwinContext.Authentication.User.Identity != null)
|
||||
{
|
||||
var claimsIdentity = context.OwinContext.Authentication.User.Identity as ClaimsIdentity;
|
||||
var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
Guid guidSession;
|
||||
if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out guidSession))
|
||||
{
|
||||
_appCtx.Services.UserService.ClearLoginSession(guidSession);
|
||||
}
|
||||
}
|
||||
|
||||
base.ResponseSignOut(context);
|
||||
|
||||
//Make sure the definitely all of these cookies are cleared when signing out with cookies
|
||||
context.Response.Cookies.Append(SessionIdValidator.CookieName, "", new CookieOptions
|
||||
{
|
||||
Expires = DateTime.Now.AddYears(-1),
|
||||
Path = "/"
|
||||
});
|
||||
context.Response.Cookies.Append(UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName, "", new CookieOptions
|
||||
{
|
||||
Expires = DateTime.Now.AddYears(-1),
|
||||
@@ -34,21 +96,45 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the culture is set correctly for the current back office user
|
||||
/// Ensures that the culture is set correctly for the current back office user and that the user's session token is valid
|
||||
/// </summary>
|
||||
/// <param name="context"/>
|
||||
/// <returns/>
|
||||
public override Task ValidateIdentity(CookieValidateIdentityContext context)
|
||||
public override async Task ValidateIdentity(CookieValidateIdentityContext context)
|
||||
{
|
||||
EnsureCulture(context);
|
||||
|
||||
await EnsureValidSessionId(context);
|
||||
|
||||
await base.ValidateIdentity(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the user has a valid session id
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// So that we are not overloading the database this throttles it's check to every minute
|
||||
/// </remarks>
|
||||
protected virtual async Task EnsureValidSessionId(CookieValidateIdentityContext context)
|
||||
{
|
||||
if (_appCtx.IsConfigured && _appCtx.IsUpgrading == false)
|
||||
await SessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context);
|
||||
}
|
||||
|
||||
private void EnsureCulture(CookieValidateIdentityContext context)
|
||||
{
|
||||
var umbIdentity = context.Identity as UmbracoBackOfficeIdentity;
|
||||
if (umbIdentity != null && umbIdentity.IsAuthenticated)
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture =
|
||||
Thread.CurrentThread.CurrentUICulture =
|
||||
new CultureInfo(umbIdentity.Culture);
|
||||
UserCultures.GetOrAdd(umbIdentity.Culture, s => new CultureInfo(s));
|
||||
}
|
||||
|
||||
return base.ValidateIdentity(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used so that we aren't creating a new CultureInfo object for every single request
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, CultureInfo> UserCultures = new ConcurrentDictionary<string, CultureInfo>();
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using Umbraco.Core.Models.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
//TODO: In v8 we need to change this to use an int? nullable TKey instead, see notes against overridden TwoFactorSignInAsync
|
||||
public class BackOfficeSignInManager : SignInManager<BackOfficeIdentityUser, int>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
@@ -222,6 +223,9 @@ namespace Umbraco.Core.Security
|
||||
user.AccessFailedCount = 0;
|
||||
await UserManager.UpdateAsync(user);
|
||||
|
||||
//set the current request's principal to the identity just signed in!
|
||||
_request.User = new ClaimsPrincipal(userIdentity);
|
||||
|
||||
_logger.WriteCore(TraceEventType.Information, 0,
|
||||
string.Format(
|
||||
"Login attempt succeeded for username {0} from IP address {1}",
|
||||
@@ -259,5 +263,67 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Two factor verification step
|
||||
/// </summary>
|
||||
/// <param name="provider"></param>
|
||||
/// <param name="code"></param>
|
||||
/// <param name="isPersistent"></param>
|
||||
/// <param name="rememberBrowser"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This is implemented because we cannot override GetVerifiedUserIdAsync and instead we have to shadow it
|
||||
/// so due to this and because we are using an INT as the TKey and not an object, it can never be null. Adding to that
|
||||
/// the default(int) value returned by the base class is always a valid user (i.e. the admin) so we just have to duplicate
|
||||
/// all of this code to check for -1 instead.
|
||||
/// </remarks>
|
||||
public override async Task<SignInStatus> TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberBrowser)
|
||||
{
|
||||
var userId = await GetVerifiedUserIdAsync();
|
||||
if (userId == -1)
|
||||
{
|
||||
return SignInStatus.Failure;
|
||||
}
|
||||
var user = await UserManager.FindByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
return SignInStatus.Failure;
|
||||
}
|
||||
if (await UserManager.IsLockedOutAsync(user.Id))
|
||||
{
|
||||
return SignInStatus.LockedOut;
|
||||
}
|
||||
if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, provider, code))
|
||||
{
|
||||
// When token is verified correctly, clear the access failed count used for lockout
|
||||
await UserManager.ResetAccessFailedCountAsync(user.Id);
|
||||
await SignInAsync(user, isPersistent, rememberBrowser);
|
||||
return SignInStatus.Success;
|
||||
}
|
||||
// If the token is incorrect, record the failure which also may cause the user to be locked out
|
||||
await UserManager.AccessFailedAsync(user.Id);
|
||||
return SignInStatus.Failure;
|
||||
}
|
||||
|
||||
/// <summary>Send a two factor code to a user</summary>
|
||||
/// <param name="provider"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This is implemented because we cannot override GetVerifiedUserIdAsync and instead we have to shadow it
|
||||
/// so due to this and because we are using an INT as the TKey and not an object, it can never be null. Adding to that
|
||||
/// the default(int) value returned by the base class is always a valid user (i.e. the admin) so we just have to duplicate
|
||||
/// all of this code to check for -1 instead.
|
||||
/// </remarks>
|
||||
public override async Task<bool> SendTwoFactorCodeAsync(string provider)
|
||||
{
|
||||
var userId = await GetVerifiedUserIdAsync();
|
||||
if (userId == -1)
|
||||
return false;
|
||||
|
||||
var token = await UserManager.GenerateTwoFactorTokenAsync(userId, provider);
|
||||
var identityResult = await UserManager.NotifyTwoFactorTokenAsync(userId, provider, token);
|
||||
return identityResult.Succeeded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,6 @@ namespace Umbraco.Core.Security
|
||||
//NOTE: This method is mostly here for backwards compat
|
||||
base.InitUserManager(manager, membershipProvider, options.DataProtectionProvider, contentSectionConfig);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -169,7 +168,7 @@ namespace Umbraco.Core.Security
|
||||
|
||||
#region What we support do not currently
|
||||
|
||||
//NOTE: Not sure if we really want/need to ever support this
|
||||
//TODO: We could support this - but a user claims will mostly just be what is in the auth cookie
|
||||
public override bool SupportsUserClaim
|
||||
{
|
||||
get { return false; }
|
||||
@@ -271,6 +270,22 @@ namespace Umbraco.Core.Security
|
||||
//manager.SmsService = new SmsService();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to validate a user's session
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<bool> ValidateSessionIdAsync(int userId, string sessionId)
|
||||
{
|
||||
var userSessionStore = Store as IUserSessionStore<BackOfficeIdentityUser, int>;
|
||||
//if this is not set, for backwards compat (which would be super rare), we'll just approve it
|
||||
if (userSessionStore == null)
|
||||
return true;
|
||||
|
||||
return await userSessionStore.ValidateSessionIdAsync(userId, sessionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will determine which password hasher to use based on what is defined in config
|
||||
/// </summary>
|
||||
@@ -674,4 +689,5 @@ namespace Umbraco.Core.Security
|
||||
return httpContext.GetCurrentRequestIpAddress();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,12 +26,13 @@ namespace Umbraco.Core.Security
|
||||
IUserRoleStore<BackOfficeIdentityUser, int>,
|
||||
IUserSecurityStampStore<BackOfficeIdentityUser, int>,
|
||||
IUserLockoutStore<BackOfficeIdentityUser, int>,
|
||||
IUserTwoFactorStore<BackOfficeIdentityUser, int>
|
||||
IUserTwoFactorStore<BackOfficeIdentityUser, int>,
|
||||
IUserSessionStore<BackOfficeIdentityUser, int>
|
||||
|
||||
//TODO: This would require additional columns/tables for now people will need to implement this on their own
|
||||
//IUserPhoneNumberStore<BackOfficeIdentityUser, int>,
|
||||
//TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation
|
||||
//IQueryableUserStore<BackOfficeIdentityUser, int>
|
||||
//TODO: This would require additional columns/tables for now people will need to implement this on their own
|
||||
//IUserPhoneNumberStore<BackOfficeIdentityUser, int>,
|
||||
//TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation
|
||||
//IQueryableUserStore<BackOfficeIdentityUser, int>
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IEntityService _entityService;
|
||||
@@ -753,6 +754,15 @@ namespace Umbraco.Core.Security
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
}
|
||||
|
||||
|
||||
public Task<bool> ValidateSessionIdAsync(int userId, string sessionId)
|
||||
{
|
||||
Guid guidSessionId;
|
||||
if (Guid.TryParse(sessionId, out guidSessionId))
|
||||
{
|
||||
return Task.FromResult(_userService.ValidateLoginSession(userId, guidSessionId));
|
||||
}
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// An IUserStore interface part to implement if the store supports validating user session Ids
|
||||
/// </summary>
|
||||
/// <typeparam name="TUser"></typeparam>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
public interface IUserSessionStore<TUser, in TKey> : IUserStore<TUser, TKey>, IDisposable
|
||||
where TUser : class, IUser<TKey>
|
||||
{
|
||||
Task<bool> ValidateSessionIdAsync(int userId, string sessionId);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
@@ -7,6 +8,37 @@ namespace Umbraco.Core.Security
|
||||
{
|
||||
public static class OwinExtensions
|
||||
{
|
||||
public static string GetCurrentRequestIpAddress(this IOwinContext owinContext)
|
||||
{
|
||||
if (owinContext == null)
|
||||
{
|
||||
return "Unknown, owinContext is null";
|
||||
}
|
||||
if (owinContext.Request == null)
|
||||
{
|
||||
return "Unknown, owinContext.Request is null";
|
||||
}
|
||||
|
||||
var httpContext = owinContext.TryGetHttpContext();
|
||||
if (httpContext == false)
|
||||
{
|
||||
return "Unknown, cannot resolve HttpContext from owinContext";
|
||||
}
|
||||
|
||||
return httpContext.Result.GetCurrentRequestIpAddress();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nasty little hack to get httpcontextbase from an owin context
|
||||
/// </summary>
|
||||
/// <param name="owinContext"></param>
|
||||
/// <returns></returns>
|
||||
internal static Attempt<HttpContextBase> TryGetHttpContext(this IOwinContext owinContext)
|
||||
{
|
||||
var ctx = owinContext.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
|
||||
return ctx == null ? Attempt<HttpContextBase>.Fail() : Attempt.Succeed(ctx);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the back office sign in manager out of OWIN
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Infrastructure;
|
||||
using Microsoft.Owin.Security.Cookies;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Static helper class used to configure a CookieAuthenticationProvider to validate a cookie against a user's session id
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This uses another cookie to track the last checked time which is done for a few reasons:
|
||||
/// * We can't use the user's auth ticket to do thsi because we'd be re-issuing the auth ticket all of the time and it would never expire
|
||||
/// plus the auth ticket size is much larger than this small value
|
||||
/// * This will execute quite often (every minute per user) and in some cases there might be several requests that end up re-issuing the cookie so the cookie value should be small
|
||||
/// * We want to avoid the user lookup if it's not required so that will only happen when the time diff is great enough in the cookie
|
||||
/// </remarks>
|
||||
internal static class SessionIdValidator
|
||||
{
|
||||
public const string CookieName = "UMB_UCONTEXT_C";
|
||||
|
||||
public static async Task ValidateSessionAsync(TimeSpan validateInterval, CookieValidateIdentityContext context)
|
||||
{
|
||||
if (context.Request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath) == false)
|
||||
return;
|
||||
|
||||
var valid = await ValidateSessionAsync(validateInterval, context.OwinContext, context.Options.CookieManager, context.Options.SystemClock, context.Properties.IssuedUtc, context.Identity);
|
||||
|
||||
if (valid == false)
|
||||
{
|
||||
context.RejectIdentity();
|
||||
context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> ValidateSessionAsync(
|
||||
TimeSpan validateInterval,
|
||||
IOwinContext owinCtx,
|
||||
ICookieManager cookieManager,
|
||||
ISystemClock systemClock,
|
||||
DateTimeOffset? authTicketIssueDate,
|
||||
ClaimsIdentity currentIdentity)
|
||||
{
|
||||
if (owinCtx == null) throw new ArgumentNullException("owinCtx");
|
||||
if (cookieManager == null) throw new ArgumentNullException("cookieManager");
|
||||
if (systemClock == null) throw new ArgumentNullException("systemClock");
|
||||
|
||||
DateTimeOffset? issuedUtc = null;
|
||||
var currentUtc = systemClock.UtcNow;
|
||||
|
||||
//read the last checked time from a custom cookie
|
||||
var lastCheckedCookie = cookieManager.GetRequestCookie(owinCtx, CookieName);
|
||||
|
||||
if (lastCheckedCookie.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
DateTimeOffset parsed;
|
||||
if (DateTimeOffset.TryParse(lastCheckedCookie, out parsed))
|
||||
{
|
||||
issuedUtc = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
//no cookie, use the issue time of the auth ticket
|
||||
if (issuedUtc.HasValue == false)
|
||||
{
|
||||
issuedUtc = authTicketIssueDate;
|
||||
}
|
||||
|
||||
// Only validate if enough time has elapsed
|
||||
var validate = issuedUtc.HasValue == false;
|
||||
if (issuedUtc.HasValue)
|
||||
{
|
||||
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
|
||||
validate = timeElapsed > validateInterval;
|
||||
}
|
||||
|
||||
if (validate == false)
|
||||
return true;
|
||||
|
||||
var manager = owinCtx.GetUserManager<BackOfficeUserManager>();
|
||||
if (manager == null)
|
||||
return false;
|
||||
|
||||
var userId = currentIdentity.GetUserId<int>();
|
||||
var user = await manager.FindByIdAsync(userId);
|
||||
if (user == null)
|
||||
return false;
|
||||
|
||||
var sessionId = currentIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
if (await manager.ValidateSessionIdAsync(userId, sessionId) == false)
|
||||
return false;
|
||||
|
||||
//we will re-issue the cookie last checked cookie
|
||||
cookieManager.AppendResponseCookie(
|
||||
owinCtx,
|
||||
CookieName,
|
||||
DateTimeOffset.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffffffzzz"),
|
||||
new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = GlobalSettings.UseSSL || owinCtx.Request.IsSecure,
|
||||
Path = "/"
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ namespace Umbraco.Core.Security
|
||||
|
||||
var username = identity.GetUserName();
|
||||
var session = identity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
var securityStamp = identity.FindFirstValue(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType);
|
||||
var startContentId = identity.FindFirstValue(Constants.Security.StartContentNodeIdClaimType);
|
||||
var startMediaId = identity.FindFirstValue(Constants.Security.StartMediaNodeIdClaimType);
|
||||
|
||||
@@ -66,8 +67,9 @@ namespace Umbraco.Core.Security
|
||||
var roles = identity.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToList();
|
||||
var allowedApps = identity.FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToList();
|
||||
|
||||
var userData = new UserData(session)
|
||||
{
|
||||
var userData = new UserData
|
||||
{
|
||||
SecurityStamp = securityStamp,
|
||||
SessionId = session,
|
||||
AllowedApplications = allowedApps.ToArray(),
|
||||
Culture = culture,
|
||||
@@ -189,7 +191,8 @@ namespace Umbraco.Core.Security
|
||||
Constants.Security.StartContentNodeIdClaimType,
|
||||
Constants.Security.StartMediaNodeIdClaimType,
|
||||
ClaimTypes.Locality,
|
||||
Constants.Security.SessionIdClaimType
|
||||
Constants.Security.SessionIdClaimType,
|
||||
Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -226,7 +229,7 @@ namespace Umbraco.Core.Security
|
||||
// by the SecurityStampValidator, see: https://katanaproject.codeplex.com/workitem/444
|
||||
if (HasClaim(x => x.Type == Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType) == false)
|
||||
{
|
||||
AddClaim(new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, SessionId, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
AddClaim(new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, SecurityStamp, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,6 +310,11 @@ namespace Umbraco.Core.Security
|
||||
get { return UserData.SessionId; }
|
||||
}
|
||||
|
||||
public string SecurityStamp
|
||||
{
|
||||
get { return UserData.SecurityStamp; }
|
||||
}
|
||||
|
||||
public string[] Roles
|
||||
{
|
||||
get { return UserData.Roles; }
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Security
|
||||
/// Use this constructor to create/assign new UserData to the ticket
|
||||
/// </summary>
|
||||
/// <param name="sessionId">
|
||||
/// The security stamp for the user
|
||||
/// The current sessionId for the user
|
||||
/// </param>
|
||||
public UserData(string sessionId)
|
||||
{
|
||||
@@ -30,11 +30,17 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the 'security stamp' for validation
|
||||
/// This is the 'sessionId' for validation
|
||||
/// </summary>
|
||||
[DataMember(Name = "sessionId")]
|
||||
public string SessionId { get; set; }
|
||||
public string SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This is the 'security stamp' for validation
|
||||
/// </summary>
|
||||
[DataMember(Name = "securityStamp")]
|
||||
public string SecurityStamp { get; set; }
|
||||
|
||||
[DataMember(Name = "id")]
|
||||
public object Id { get; set; }
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
namespace Umbraco.Core.Services
|
||||
@@ -22,5 +26,86 @@ namespace Umbraco.Core.Services
|
||||
uow.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns paged items in the audit trail for a given entity
|
||||
/// </summary>
|
||||
/// <param name="entityId"></param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <param name="totalRecords"></param>
|
||||
/// <param name="orderDirection">
|
||||
/// By default this will always be ordered descending (newest first)
|
||||
/// </param>
|
||||
/// <param name="auditTypeFilter">
|
||||
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
|
||||
/// so we need to do that here
|
||||
/// </param>
|
||||
/// <param name="customFilter">
|
||||
/// Optional filter to be applied
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IAuditItem> GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize, out long totalRecords,
|
||||
Direction orderDirection = Direction.Descending,
|
||||
AuditType[] auditTypeFilter = null,
|
||||
IQuery<IAuditItem> customFilter = null)
|
||||
{
|
||||
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
|
||||
Mandate.ParameterCondition(pageSize > 0, "pageSize");
|
||||
|
||||
if (entityId == Constants.System.Root || entityId <= 0)
|
||||
{
|
||||
totalRecords = 0;
|
||||
return Enumerable.Empty<IAuditItem>();
|
||||
}
|
||||
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
{
|
||||
var repository = RepositoryFactory.CreateAuditRepository(uow);
|
||||
|
||||
var query = Query<IAuditItem>.Builder.Where(x => x.Id == entityId);
|
||||
|
||||
return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns paged items in the audit trail for a given user
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <param name="totalRecords"></param>
|
||||
/// <param name="orderDirection">
|
||||
/// By default this will always be ordered descending (newest first)
|
||||
/// </param>
|
||||
/// <param name="auditTypeFilter">
|
||||
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
|
||||
/// so we need to do that here
|
||||
/// </param>
|
||||
/// <param name="customFilter">
|
||||
/// Optional filter to be applied
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IAuditItem> GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords, Direction orderDirection = Direction.Descending, AuditType[] auditTypeFilter = null, IQuery<IAuditItem> customFilter = null)
|
||||
{
|
||||
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
|
||||
Mandate.ParameterCondition(pageSize > 0, "pageSize");
|
||||
|
||||
if (userId < 0)
|
||||
{
|
||||
totalRecords = 0;
|
||||
return Enumerable.Empty<IAuditItem>();
|
||||
}
|
||||
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
{
|
||||
var repository = RepositoryFactory.CreateAuditRepository(uow);
|
||||
|
||||
var query = Query<IAuditItem>.Builder.Where(x => x.UserId == userId);
|
||||
|
||||
return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ namespace Umbraco.Core.Services
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
{
|
||||
var repository = RepositoryFactory.CreateContentRepository(uow);
|
||||
return repository.CountPublished();
|
||||
return repository.CountPublished(contentTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2453,7 +2453,6 @@ namespace Umbraco.Core.Services
|
||||
|
||||
Audit(uow, AuditType.Publish, "Save and Publish performed by user", userId, content.Id);
|
||||
uow.Commit();
|
||||
|
||||
return Attempt.If(publishStatus.StatusType == PublishStatusType.Success, publishStatus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace Umbraco.Core.Services
|
||||
xml.Add(new XAttribute("writerID", content.WriterId));
|
||||
xml.Add(new XAttribute("template", content.Template == null ? "0" : content.Template.Id.ToString(CultureInfo.InvariantCulture)));
|
||||
xml.Add(new XAttribute("nodeTypeAlias", content.ContentType.Alias));
|
||||
xml.Add(new XAttribute("isPublished", content.Published));
|
||||
|
||||
if (deep)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
|
||||
namespace Umbraco.Core.Services
|
||||
{
|
||||
public interface IAuditService : IService
|
||||
{
|
||||
void Add(AuditType type, string comment, int userId, int objectId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns paged items in the audit trail for a given entity
|
||||
/// </summary>
|
||||
/// <param name="entityId"></param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <param name="totalRecords"></param>
|
||||
/// <param name="orderDirection">
|
||||
/// By default this will always be ordered descending (newest first)
|
||||
/// </param>
|
||||
/// <param name="auditTypeFilter">
|
||||
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
|
||||
/// so we need to do that here
|
||||
/// </param>
|
||||
/// <param name="customFilter">
|
||||
/// Optional filter to be applied
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IAuditItem> GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize, out long totalRecords,
|
||||
Direction orderDirection = Direction.Descending,
|
||||
AuditType[] auditTypeFilter = null,
|
||||
IQuery<IAuditItem> customFilter = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns paged items in the audit trail for a given user
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <param name="totalRecords"></param>
|
||||
/// <param name="orderDirection">
|
||||
/// By default this will always be ordered descending (newest first)
|
||||
/// </param>
|
||||
/// <param name="auditTypeFilter">
|
||||
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
|
||||
/// so we need to do that here
|
||||
/// </param>
|
||||
/// <param name="customFilter">
|
||||
/// Optional filter to be applied
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IAuditItem> GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords,
|
||||
Direction orderDirection = Direction.Descending,
|
||||
AuditType[] auditTypeFilter = null,
|
||||
IQuery<IAuditItem> customFilter = null);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -11,7 +12,35 @@ namespace Umbraco.Core.Services
|
||||
/// Defines the UserService, which is an easy access to operations involving <see cref="IProfile"/> and eventually Users.
|
||||
/// </summary>
|
||||
public interface IUserService : IMembershipUserService
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a database entry for starting a new login session for a user
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="requestingIpAddress"></param>
|
||||
/// <returns></returns>
|
||||
Guid CreateLoginSession(int userId, string requestingIpAddress);
|
||||
|
||||
/// <summary>
|
||||
/// Validates that a user login session is valid/current and hasn't been closed
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <returns></returns>
|
||||
bool ValidateLoginSession(int userId, Guid sessionId);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the session's validity
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
void ClearLoginSession(Guid sessionId);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all valid sessions for the user
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
int ClearLoginSessions(int userId);
|
||||
|
||||
/// <summary>
|
||||
/// This is basically facets of UserStates key = state, value = count
|
||||
/// </summary>
|
||||
|
||||
@@ -106,8 +106,8 @@ namespace Umbraco.Core.Services
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
_id2Key[id] = new TypedId<Guid>(val.Value, umbracoObjectType); ;
|
||||
_key2Id[val.Value] = new TypedId<int>();
|
||||
_id2Key[id] = new TypedId<Guid>(val.Value, umbracoObjectType);
|
||||
_key2Id[val.Value] = new TypedId<int>(id, umbracoObjectType);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -12,7 +11,6 @@ using Umbraco.Core.Events;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
@@ -43,7 +41,7 @@ namespace Umbraco.Core.Services
|
||||
if (userService == null) throw new ArgumentNullException("userService");
|
||||
_dataTypeService = dataTypeService;
|
||||
_userService = userService;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IMedia"/> object using the alias of the <see cref="IMediaType"/>
|
||||
@@ -63,7 +61,7 @@ namespace Umbraco.Core.Services
|
||||
{
|
||||
var parent = GetById(parentId);
|
||||
return CreateMedia(name, parent, mediaTypeAlias, userId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IMedia"/> object using the alias of the <see cref="IMediaType"/>
|
||||
@@ -731,65 +729,10 @@ namespace Umbraco.Core.Services
|
||||
/// <returns><see cref="IMedia"/></returns>
|
||||
public IMedia GetMediaByPath(string mediaPath)
|
||||
{
|
||||
var umbracoFileValue = mediaPath;
|
||||
|
||||
const string Pattern = ".*[_][0-9]+[x][0-9]+[.].*";
|
||||
var isResized = Regex.IsMatch(mediaPath, Pattern);
|
||||
|
||||
// If the image has been resized we strip the "_403x328" of the original "/media/1024/koala_403x328.jpg" url.
|
||||
if (isResized)
|
||||
{
|
||||
var underscoreIndex = mediaPath.LastIndexOf('_');
|
||||
var dotIndex = mediaPath.LastIndexOf('.');
|
||||
umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex));
|
||||
}
|
||||
|
||||
Func<string, Sql> createSql = url => new Sql().Select("*")
|
||||
.From<PropertyDataDto>()
|
||||
.InnerJoin<PropertyTypeDto>()
|
||||
.On<PropertyDataDto, PropertyTypeDto>(left => left.PropertyTypeId, right => right.Id)
|
||||
.Where<PropertyTypeDto>(x => x.Alias == "umbracoFile")
|
||||
.Where<PropertyDataDto>(x => x.VarChar == url);
|
||||
|
||||
var sql = createSql(umbracoFileValue);
|
||||
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
{
|
||||
var propertyDataDto = uow.Database.Fetch<PropertyDataDto, PropertyTypeDto>(sql).FirstOrDefault();
|
||||
|
||||
// If the stripped-down url returns null, we try again with the original url.
|
||||
// Previously, the function would fail on e.g. "my_x_image.jpg"
|
||||
if (propertyDataDto == null)
|
||||
{
|
||||
sql = createSql(mediaPath);
|
||||
propertyDataDto = uow.Database.Fetch<PropertyDataDto, PropertyTypeDto>(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<PropertyDataDto>()
|
||||
.InnerJoin<PropertyTypeDto>()
|
||||
.On<PropertyDataDto, PropertyTypeDto>(left => left.PropertyTypeId, right => right.Id)
|
||||
.Where<PropertyTypeDto>(x => x.Alias == "umbracoFile")
|
||||
.Where("dataNtext LIKE @0", "%" + umbracoFileValue + "%");
|
||||
propertyDataDto = uow.Database.Fetch<PropertyDataDto, PropertyTypeDto>(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<PropertyDataDto>()
|
||||
.InnerJoin<PropertyTypeDto>()
|
||||
.On<PropertyDataDto, PropertyTypeDto>(left => left.PropertyTypeId, right => right.Id)
|
||||
.Where<PropertyTypeDto>(x => x.Alias == "umbracoFile")
|
||||
.Where("dataNvarchar LIKE @0", "%" + umbracoFileValue + "%");
|
||||
propertyDataDto = uow.Database.Fetch<PropertyDataDto, PropertyTypeDto>(nvarcharQuery).FirstOrDefault();
|
||||
}
|
||||
|
||||
return propertyDataDto == null ? null : GetById(propertyDataDto.NodeId);
|
||||
var repository = RepositoryFactory.CreateMediaRepository(uow);
|
||||
return repository.GetMediaByPath(mediaPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1021,7 +964,7 @@ namespace Umbraco.Core.Services
|
||||
|
||||
if (raiseEvents)
|
||||
{
|
||||
saveEventArgs.CanCancel = false;
|
||||
saveEventArgs.CanCancel = false;
|
||||
uow.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
}
|
||||
|
||||
|
||||
@@ -558,6 +558,47 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public Guid CreateLoginSession(int userId, string requestingIpAddress)
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
var repository = RepositoryFactory.CreateUserRepository(uow);
|
||||
var session = repository.CreateLoginSession(userId, requestingIpAddress);
|
||||
uow.Commit();
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
public int ClearLoginSessions(int userId)
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
var repository = RepositoryFactory.CreateUserRepository(uow);
|
||||
var count = repository.ClearLoginSessions(userId);
|
||||
uow.Commit();
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearLoginSession(Guid sessionId)
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
var repository = RepositoryFactory.CreateUserRepository(uow);
|
||||
repository.ClearLoginSession(sessionId);
|
||||
uow.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
public bool ValidateLoginSession(int userId, Guid sessionId)
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
{
|
||||
var repository = RepositoryFactory.CreateUserRepository(uow);
|
||||
return repository.ValidateLoginSession(userId, sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
public IDictionary<UserState, int> GetUserStates()
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
|
||||
@@ -14,6 +14,7 @@ using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence;
|
||||
using umbraco.interfaces;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Sync
|
||||
@@ -39,6 +40,7 @@ namespace Umbraco.Core.Sync
|
||||
private bool _syncing;
|
||||
private bool _released;
|
||||
private readonly ProfilingLogger _profilingLogger;
|
||||
private readonly Lazy<string> _distCacheFilePath = new Lazy<string>(GetDistCacheFilePath);
|
||||
|
||||
protected DatabaseServerMessengerOptions Options { get; private set; }
|
||||
protected ApplicationContext ApplicationContext { get { return _appContext; } }
|
||||
@@ -85,7 +87,8 @@ namespace Umbraco.Core.Sync
|
||||
{
|
||||
UtcStamp = DateTime.UtcNow,
|
||||
Instructions = JsonConvert.SerializeObject(instructions, Formatting.None),
|
||||
OriginIdentity = LocalIdentity
|
||||
OriginIdentity = LocalIdentity,
|
||||
InstructionCount = instructions.Sum(x => x.JsonIdCount)
|
||||
};
|
||||
|
||||
ApplicationContext.DatabaseContext.Database.Insert(dto);
|
||||
@@ -163,10 +166,10 @@ namespace Umbraco.Core.Sync
|
||||
}
|
||||
else
|
||||
{
|
||||
//check for how many instructions there are to process
|
||||
//TODO: In 7.6 we need to store the count of instructions per row since this is not affective because there can be far more than one (if not thousands)
|
||||
// of instructions in a single row.
|
||||
var count = _appContext.DatabaseContext.Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId});
|
||||
//check for how many instructions there are to process, each row contains a count of the number of instructions contained in each
|
||||
//row so we will sum these numbers to get the actual count.
|
||||
var count = _appContext.DatabaseContext.Database
|
||||
.ExecuteScalar<int>("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId});
|
||||
if (count > Options.MaxProcessingInstructionCount)
|
||||
{
|
||||
//too many instructions, proceed to cold boot
|
||||
@@ -203,7 +206,7 @@ namespace Umbraco.Core.Sync
|
||||
/// <summary>
|
||||
/// Synchronize the server (throttled).
|
||||
/// </summary>
|
||||
protected void Sync()
|
||||
protected internal void Sync()
|
||||
{
|
||||
lock (_locko)
|
||||
{
|
||||
@@ -460,10 +463,9 @@ namespace Umbraco.Core.Sync
|
||||
/// </remarks>
|
||||
private void ReadLastSynced()
|
||||
{
|
||||
var path = SyncFilePath;
|
||||
if (File.Exists(path) == false) return;
|
||||
if (File.Exists(_distCacheFilePath.Value) == false) return;
|
||||
|
||||
var content = File.ReadAllText(path);
|
||||
var content = File.ReadAllText(_distCacheFilePath.Value);
|
||||
int last;
|
||||
if (int.TryParse(content, out last))
|
||||
_lastId = last;
|
||||
@@ -478,7 +480,7 @@ namespace Umbraco.Core.Sync
|
||||
/// </remarks>
|
||||
private void SaveLastSynced(int id)
|
||||
{
|
||||
File.WriteAllText(SyncFilePath, id.ToString(CultureInfo.InvariantCulture));
|
||||
File.WriteAllText(_distCacheFilePath.Value, id.ToString(CultureInfo.InvariantCulture));
|
||||
_lastId = id;
|
||||
}
|
||||
|
||||
@@ -498,20 +500,40 @@ namespace Umbraco.Core.Sync
|
||||
+ "/D" + AppDomain.CurrentDomain.Id // eg 22
|
||||
+ "] " + Guid.NewGuid().ToString("N").ToUpper(); // make it truly unique
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sync file path for the local server.
|
||||
/// </summary>
|
||||
/// <returns>The sync file path for the local server.</returns>
|
||||
private static string SyncFilePath
|
||||
private static string GetDistCacheFilePath()
|
||||
{
|
||||
get
|
||||
{
|
||||
var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/DistCache/" + NetworkHelper.FileSafeMachineName);
|
||||
if (Directory.Exists(tempFolder) == false)
|
||||
Directory.CreateDirectory(tempFolder);
|
||||
var fileName = HttpRuntime.AppDomainAppId.ReplaceNonAlphanumericChars(string.Empty) + "-lastsynced.txt";
|
||||
|
||||
return Path.Combine(tempFolder, HttpRuntime.AppDomainAppId.ReplaceNonAlphanumericChars(string.Empty) + "-lastsynced.txt");
|
||||
string distCacheFilePath;
|
||||
switch (GlobalSettings.LocalTempStorageLocation)
|
||||
{
|
||||
case LocalTempStorage.AspNetTemp:
|
||||
distCacheFilePath = Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData", fileName);
|
||||
break;
|
||||
case LocalTempStorage.EnvironmentTemp:
|
||||
var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1();
|
||||
var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData",
|
||||
//include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back
|
||||
// to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not
|
||||
// utilizing an old path
|
||||
appDomainHash);
|
||||
distCacheFilePath = Path.Combine(cachePath, fileName);
|
||||
break;
|
||||
case LocalTempStorage.Default:
|
||||
default:
|
||||
var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/DistCache");
|
||||
distCacheFilePath = Path.Combine(tempFolder, fileName);
|
||||
break;
|
||||
}
|
||||
|
||||
//ensure the folder exists
|
||||
var folder = Path.GetDirectoryName(distCacheFilePath);
|
||||
if (folder == null)
|
||||
throw new InvalidOperationException("The folder could not be determined for the file " + distCacheFilePath);
|
||||
if (Directory.Exists(folder) == false)
|
||||
Directory.CreateDirectory(folder);
|
||||
|
||||
return distCacheFilePath;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -18,7 +18,10 @@ namespace Umbraco.Core.Sync
|
||||
// need this public, parameter-less constructor so the web service messenger
|
||||
// can de-serialize the instructions it receives
|
||||
public RefreshInstruction()
|
||||
{ }
|
||||
{
|
||||
//set default - this value is not used for reading after it's been deserialized, it's only used for persisting the instruction to the db
|
||||
JsonIdCount = 1;
|
||||
}
|
||||
|
||||
// need this public one so it can be de-serialized - used by the Json thing
|
||||
// otherwise, should use GetInstructions(...)
|
||||
@@ -30,12 +33,16 @@ namespace Umbraco.Core.Sync
|
||||
IntId = intId;
|
||||
JsonIds = jsonIds;
|
||||
JsonPayload = jsonPayload;
|
||||
//set default - this value is not used for reading after it's been deserialized, it's only used for persisting the instruction to the db
|
||||
JsonIdCount = 1;
|
||||
}
|
||||
|
||||
private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType)
|
||||
{
|
||||
RefresherId = refresher.UniqueIdentifier;
|
||||
RefreshType = refreshType;
|
||||
//set default - this value is not used for reading after it's been deserialized, it's only used for persisting the instruction to the db
|
||||
JsonIdCount = 1;
|
||||
}
|
||||
|
||||
private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, Guid guidId)
|
||||
@@ -50,9 +57,21 @@ namespace Umbraco.Core.Sync
|
||||
IntId = intId;
|
||||
}
|
||||
|
||||
private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, string json)
|
||||
/// <summary>
|
||||
/// A private constructor to create a new instance
|
||||
/// </summary>
|
||||
/// <param name="refresher"></param>
|
||||
/// <param name="refreshType"></param>
|
||||
/// <param name="json"></param>
|
||||
/// <param name="idCount">
|
||||
/// When the refresh method is <see cref="RefreshMethodType.RefreshByIds"/> we know how many Ids are being refreshed so we know the instruction
|
||||
/// count which will be taken into account when we store this count in the database.
|
||||
/// </param>
|
||||
private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, string json, int idCount = 1)
|
||||
: this(refresher, refreshType)
|
||||
{
|
||||
JsonIdCount = idCount;
|
||||
|
||||
if (refreshType == RefreshMethodType.RefreshByJson)
|
||||
JsonPayload = json;
|
||||
else
|
||||
@@ -77,8 +96,12 @@ namespace Umbraco.Core.Sync
|
||||
case MessageType.RefreshById:
|
||||
if (idType == null)
|
||||
throw new InvalidOperationException("Cannot refresh by id if idType is null.");
|
||||
if (idType == typeof (int)) // bulk of ints is supported
|
||||
return new[] { new RefreshInstruction(refresher, RefreshMethodType.RefreshByIds, JsonConvert.SerializeObject(ids.Cast<int>().ToArray())) };
|
||||
if (idType == typeof(int))
|
||||
{
|
||||
// bulk of ints is supported
|
||||
var intIds = ids.Cast<int>().ToArray();
|
||||
return new[] { new RefreshInstruction(refresher, RefreshMethodType.RefreshByIds, JsonConvert.SerializeObject(intIds), intIds.Length) };
|
||||
}
|
||||
// else must be guids, bulk of guids is not supported, iterate
|
||||
return ids.Select(x => new RefreshInstruction(refresher, RefreshMethodType.RefreshByGuid, (Guid) x));
|
||||
|
||||
@@ -121,6 +144,14 @@ namespace Umbraco.Core.Sync
|
||||
/// </summary>
|
||||
public string JsonIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of Ids contained in the JsonIds json value
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used to determine the instruction count per row
|
||||
/// </remarks>
|
||||
public int JsonIdCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the payload data value.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using System.Web.Compilation;
|
||||
using System.Web.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
@@ -21,6 +20,38 @@ namespace Umbraco.Core
|
||||
{
|
||||
private static volatile HashSet<Assembly> _localFilteredAssemblyCache;
|
||||
private static readonly object LocalFilteredAssemblyCacheLocker = new object();
|
||||
private static readonly List<string> NotifiedLoadExceptionAssemblies = new List<string>();
|
||||
private static string[] _assembliesAcceptingLoadExceptions;
|
||||
|
||||
private static string[] AssembliesAcceptingLoadExceptions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_assembliesAcceptingLoadExceptions != null)
|
||||
return _assembliesAcceptingLoadExceptions;
|
||||
|
||||
var s = ConfigurationManager.AppSettings["Umbraco.AssembliesAcceptingLoadExceptions"];
|
||||
return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s)
|
||||
? new string[0]
|
||||
: s.Split(',').Select(x => x.Trim()).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AcceptsLoadExceptions(Assembly a)
|
||||
{
|
||||
if (AssembliesAcceptingLoadExceptions.Length == 0)
|
||||
return false;
|
||||
if (AssembliesAcceptingLoadExceptions.Length == 1 && AssembliesAcceptingLoadExceptions[0] == "*")
|
||||
return true;
|
||||
var name = a.GetName().Name; // simple name of the assembly
|
||||
return AssembliesAcceptingLoadExceptions.Any(pattern =>
|
||||
{
|
||||
if (pattern.Length > name.Length) return false; // pattern longer than name
|
||||
if (pattern.Length == name.Length) return pattern.InvariantEquals(name); // same length, must be identical
|
||||
if (pattern[pattern.Length] != '.') return false; // pattern is shorter than name, must end with dot
|
||||
return name.StartsWith(pattern); // and name must start with pattern
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// lazily load a reference to all assemblies and only local assemblies.
|
||||
@@ -45,7 +76,7 @@ namespace Umbraco.Core
|
||||
HashSet<Assembly> assemblies = null;
|
||||
try
|
||||
{
|
||||
var isHosted = HttpContext.Current != null || HostingEnvironment.IsHosted;
|
||||
var isHosted = IOHelper.IsHosted;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -528,9 +559,22 @@ namespace Umbraco.Core
|
||||
AppendCouldNotLoad(sb, a, getAll);
|
||||
foreach (var loaderException in rex.LoaderExceptions.WhereNotNull())
|
||||
AppendLoaderException(sb, loaderException);
|
||||
|
||||
var ex = new ReflectionTypeLoadException(rex.Types, rex.LoaderExceptions, sb.ToString());
|
||||
|
||||
// rethrow with new message
|
||||
throw new ReflectionTypeLoadException(rex.Types, rex.LoaderExceptions, sb.ToString());
|
||||
// rethrow with new message, unless accepted
|
||||
if (AcceptsLoadExceptions(a) == false) throw ex;
|
||||
|
||||
// log a warning, and return what we can
|
||||
lock (NotifiedLoadExceptionAssemblies)
|
||||
{
|
||||
if (NotifiedLoadExceptionAssemblies.Contains(a.FullName) == false)
|
||||
{
|
||||
NotifiedLoadExceptionAssemblies.Add(a.FullName);
|
||||
LogHelper.WarnWithException(typeof(TypeFinder), "Could not load all types from " + a.GetName().Name + ".", ex);
|
||||
}
|
||||
}
|
||||
return rex.Types.WhereNotNull().ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,12 +52,8 @@
|
||||
<HintPath>..\packages\HtmlAgilityPack.1.4.9.5\lib\Net45\HtmlAgilityPack.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImageProcessor, Version=2.5.3.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ImageProcessor.2.5.3\lib\net45\ImageProcessor.dll</HintPath>
|
||||
<Reference Include="ImageProcessor, Version=2.5.6.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ImageProcessor.2.5.6\lib\net45\ImageProcessor.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\log4net.2.0.8\lib\net45-full\log4net.dll</HintPath>
|
||||
@@ -117,6 +113,8 @@
|
||||
</Reference>
|
||||
<Reference Include="System.DirectoryServices.AccountManagement" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Linq.Expressions" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Net.Http.WebRequest" />
|
||||
@@ -198,7 +196,7 @@
|
||||
<Compile Include="Configuration\BaseRest\ExtensionElement.cs" />
|
||||
<Compile Include="Configuration\BaseRest\ExtensionElementCollection.cs" />
|
||||
<Compile Include="Configuration\BaseRest\MethodElement.cs" />
|
||||
<Compile Include="Configuration\ContentXmlStorage.cs" />
|
||||
<Compile Include="Configuration\LocalTempStorage.cs" />
|
||||
<Compile Include="Configuration\CoreDebug.cs" />
|
||||
<Compile Include="Configuration\Dashboard\AccessElement.cs" />
|
||||
<Compile Include="Configuration\Dashboard\AccessItem.cs" />
|
||||
@@ -248,6 +246,7 @@
|
||||
<Compile Include="Configuration\UmbracoConfigurationSection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\AppCodeFileExtensionsCollection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\AppCodeFileExtensionsElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\BackOfficeElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\CharCollection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\CharElement.cs" />
|
||||
<Compile Include="Configuration\CommaDelimitedConfigurationElement.cs" />
|
||||
@@ -256,6 +255,7 @@
|
||||
<Compile Include="Configuration\UmbracoSettings\ContentErrorPageElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ContentErrorsElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ContentSectionExtensions.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\IBackOfficeSection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ImagingAutoFillPropertiesCollection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ImagingAutoFillUploadFieldElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ContentImagingElement.cs" />
|
||||
@@ -291,6 +291,7 @@
|
||||
<Compile Include="Configuration\UmbracoSettings\ISecuritySection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\IServer.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ITemplatesSection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ITourSection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\IUmbracoSettingsSection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\IViewStateMoverModuleSection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\IWebRoutingSection.cs" />
|
||||
@@ -321,6 +322,7 @@
|
||||
<Compile Include="Configuration\UmbracoSettings\ServerCollection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ServerElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\TemplatesElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\TourConfigElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\UmbracoConfigurationElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\UmbracoSettingsSection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\UrlReplacingElement.cs" />
|
||||
@@ -368,6 +370,7 @@
|
||||
<Compile Include="IHttpContextAccessor.cs" />
|
||||
<Compile Include="Models\EntityBase\EntityPath.cs" />
|
||||
<Compile Include="Models\EntityBase\IDeletableEntity.cs" />
|
||||
<Compile Include="Models\IAuditItem.cs" />
|
||||
<Compile Include="Models\IUserControl.cs" />
|
||||
<Compile Include="Models\Membership\ContentPermissionSet.cs" />
|
||||
<Compile Include="Models\Membership\EntityPermissionCollection.cs" />
|
||||
@@ -379,6 +382,8 @@
|
||||
<Compile Include="Models\Membership\UserState.cs" />
|
||||
<Compile Include="Models\Membership\UserType.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedContentTypeConverter.cs" />
|
||||
<Compile Include="Models\Rdbms\MediaDto.cs" />
|
||||
<Compile Include="Models\Rdbms\UserLoginDto.cs" />
|
||||
<Compile Include="Models\Rdbms\UserStartNodeDto.cs" />
|
||||
<Compile Include="Models\UserControl.cs" />
|
||||
<Compile Include="OrderedHashSet.cs" />
|
||||
@@ -540,12 +545,18 @@
|
||||
<Compile Include="Persistence\LockedRepository.cs" />
|
||||
<Compile Include="Persistence\LockingRepository.cs" />
|
||||
<Compile Include="Persistence\Mappers\AccessMapper.cs" />
|
||||
<Compile Include="Persistence\Mappers\AuditMapper.cs" />
|
||||
<Compile Include="Persistence\Mappers\DomainMapper.cs" />
|
||||
<Compile Include="Persistence\Mappers\ExternalLoginMapper.cs" />
|
||||
<Compile Include="Persistence\Mappers\MigrationEntryMapper.cs" />
|
||||
<Compile Include="Persistence\Mappers\UserGroupMapper.cs" />
|
||||
<Compile Include="Persistence\Migrations\LocalMigrationContext.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionFourOneZero\AddPreviewXmlTable.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddTourDataUserColumn.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddIndexToPropertyTypeAliasColumn.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddInstructionCountColumn.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddCmsMediaTable.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddUserLoginTable.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\AddIndexToDictionaryKeyColumn.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\EnsureContentTemplatePermissions.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\ReduceDictionaryKeyColumnsSize.cs" />
|
||||
@@ -693,11 +704,13 @@
|
||||
<Compile Include="Security\IBackOfficeUserPasswordChecker.cs" />
|
||||
<Compile Include="Security\IMembershipProviderPasswordHasher.cs" />
|
||||
<Compile Include="Security\IUserAwarePasswordHasher.cs" />
|
||||
<Compile Include="Security\IUserSessionStore.cs" />
|
||||
<Compile Include="Security\MachineKeyGenerator.cs" />
|
||||
<Compile Include="Security\MembershipProviderPasswordHasher.cs" />
|
||||
<Compile Include="Security\EmailService.cs" />
|
||||
<Compile Include="Security\MembershipProviderPasswordValidator.cs" />
|
||||
<Compile Include="Security\OwinExtensions.cs" />
|
||||
<Compile Include="Security\SessionIdValidator.cs" />
|
||||
<Compile Include="Security\UmbracoEmailMessage.cs" />
|
||||
<Compile Include="Security\UserAwareMembershipProviderPasswordHasher.cs" />
|
||||
<Compile Include="SemVersionExtensions.cs" />
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<package id="AutoMapper" version="3.3.1" targetFramework="net45" />
|
||||
<package id="ClientDependency" version="1.9.2" targetFramework="net45" />
|
||||
<package id="HtmlAgilityPack" version="1.4.9.5" targetFramework="net45" />
|
||||
<package id="ImageProcessor" version="2.5.3" targetFramework="net45" />
|
||||
<package id="ImageProcessor" version="2.5.6" targetFramework="net45" />
|
||||
<package id="log4net" version="2.0.8" targetFramework="net45" />
|
||||
<package id="Log4Net.Async" version="2.0.4" targetFramework="net45" />
|
||||
<package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net45" />
|
||||
@@ -17,6 +17,5 @@
|
||||
<package id="Newtonsoft.Json" version="10.0.2" targetFramework="net45" />
|
||||
<package id="Owin" version="1.0" targetFramework="net45" />
|
||||
<package id="semver" version="1.1.2" targetFramework="net45" />
|
||||
<package id="SharpZipLib" version="0.86.0" targetFramework="net45" />
|
||||
<package id="SqlServerCE" version="4.0.0.1" targetFramework="net45" />
|
||||
</packages>
|
||||
@@ -20,7 +20,7 @@ using Umbraco.Tests.TestHelpers.Entities;
|
||||
namespace Umbraco.Tests.Models
|
||||
{
|
||||
[TestFixture]
|
||||
public class ContentTests : BaseUmbracoConfigurationTest
|
||||
public class ContentTests : BaseUmbracoApplicationTest
|
||||
{
|
||||
[SetUp]
|
||||
public void Init()
|
||||
|
||||
@@ -121,8 +121,7 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
{
|
||||
AssertDisplayProperty(result, p, ApplicationContext);
|
||||
}
|
||||
Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count() - 1);
|
||||
Assert.IsTrue(result.Tabs.Any(x => x.Label == ui.Text("general", "properties")));
|
||||
Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count());
|
||||
Assert.IsTrue(result.Tabs.First().IsActive);
|
||||
Assert.IsTrue(result.Tabs.Except(new[] {result.Tabs.First()}).All(x => x.IsActive == false));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Packaging;
|
||||
|
||||
namespace Umbraco.Tests.Packaging
|
||||
{
|
||||
[TestFixture]
|
||||
class PackageExtractionTests
|
||||
{
|
||||
private const string PackageFileName = "Document_Type_Picker_1.1.umb";
|
||||
|
||||
private static string GetTestPackagePath(string packageName)
|
||||
{
|
||||
const string testPackagesDirName = "Packaging\\Packages";
|
||||
string path = Path.Combine(Core.Configuration.GlobalSettings.FullpathToRoot, testPackagesDirName, packageName);
|
||||
return path;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadFilesFromArchive_NumberOfFilesIs1_SearchingForPackageXmlFile()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new PackageExtraction();
|
||||
|
||||
// Act
|
||||
var result = sut.ReadFilesFromArchive(GetTestPackagePath(PackageFileName), new[] { "Package.xml" });
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, result.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FindMissingFiles_1_UnknownFile()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new PackageExtraction();
|
||||
|
||||
// Act
|
||||
var result = sut.FindMissingFiles(GetTestPackagePath(PackageFileName), new[] { "DoesNotExists.XYZ" });
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, result.Count());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,5 +40,86 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
Assert.That(dtos.Any(), Is.True);
|
||||
Assert.That(dtos.First().Comment, Is.EqualTo("This is a System audit trail"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Paged_Items()
|
||||
{
|
||||
var provider = new PetaPocoUnitOfWorkProvider(Logger);
|
||||
var unitOfWork = provider.GetUnitOfWork();
|
||||
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
repo.AddOrUpdate(new AuditItem(i, string.Format("Content {0} created", i), AuditType.New, 0));
|
||||
repo.AddOrUpdate(new AuditItem(i, string.Format("Content {0} published", i), AuditType.Publish, 0));
|
||||
}
|
||||
unitOfWork.Commit();
|
||||
}
|
||||
|
||||
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
|
||||
{
|
||||
long total;
|
||||
var page = repo.GetPagedResultsByQuery(Query<IAuditItem>.Builder, 0, 10, out total, Direction.Descending, null, null);
|
||||
|
||||
Assert.AreEqual(10, page.Count());
|
||||
Assert.AreEqual(200, total);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Paged_Items_With_AuditType_Filter()
|
||||
{
|
||||
var provider = new PetaPocoUnitOfWorkProvider(Logger);
|
||||
var unitOfWork = provider.GetUnitOfWork();
|
||||
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
repo.AddOrUpdate(new AuditItem(i, string.Format("Content {0} created", i), AuditType.New, 0));
|
||||
repo.AddOrUpdate(new AuditItem(i, string.Format("Content {0} published", i), AuditType.Publish, 0));
|
||||
}
|
||||
unitOfWork.Commit();
|
||||
}
|
||||
|
||||
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
|
||||
{
|
||||
long total;
|
||||
var page = repo.GetPagedResultsByQuery(Query<IAuditItem>.Builder, 0, 9, out total, Direction.Descending,
|
||||
new[] {AuditType.Publish}, null)
|
||||
.ToArray();
|
||||
|
||||
Assert.AreEqual(9, page.Length);
|
||||
Assert.IsTrue(page.All(x => x.AuditType == AuditType.Publish));
|
||||
Assert.AreEqual(100, total);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Paged_Items_With_Custom_Filter()
|
||||
{
|
||||
var provider = new PetaPocoUnitOfWorkProvider(Logger);
|
||||
var unitOfWork = provider.GetUnitOfWork();
|
||||
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
repo.AddOrUpdate(new AuditItem(i, "Content created", AuditType.New, 0));
|
||||
repo.AddOrUpdate(new AuditItem(i, "Content published", AuditType.Publish, 0));
|
||||
}
|
||||
unitOfWork.Commit();
|
||||
}
|
||||
|
||||
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
|
||||
{
|
||||
long total;
|
||||
var page = repo.GetPagedResultsByQuery(Query<IAuditItem>.Builder, 0, 8, out total, Direction.Descending,
|
||||
null, Query<IAuditItem>.Builder.Where(item => item.Comment == "Content created"))
|
||||
.ToArray();
|
||||
|
||||
Assert.AreEqual(8, page.Length);
|
||||
Assert.IsTrue(page.All(x => x.Comment == "Content created"));
|
||||
Assert.AreEqual(100, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,7 +271,7 @@ AnotherContentFinder
|
||||
public void Resolves_Assigned_Mappers()
|
||||
{
|
||||
var foundTypes1 = _manager.ResolveAssignedMapperTypes();
|
||||
Assert.AreEqual(28, foundTypes1.Count());
|
||||
Assert.AreEqual(29, foundTypes1.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Umbraco.Tests.Security
|
||||
public void Create_From_Claims_Identity()
|
||||
{
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var securityStamp = Guid.NewGuid().ToString();
|
||||
var claimsIdentity = new ClaimsIdentity(new[]
|
||||
{
|
||||
//This is the id that 'identity' uses to check for the user id
|
||||
@@ -36,12 +37,14 @@ namespace Umbraco.Tests.Security
|
||||
new Claim(ClaimTypes.Locality, "en-us", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
new Claim(Constants.Security.SessionIdClaimType, sessionId, Constants.Security.SessionIdClaimType, TestIssuer, TestIssuer),
|
||||
new Claim(ClaimsIdentity.DefaultRoleClaimType, "admin", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, securityStamp, ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
});
|
||||
|
||||
var backofficeIdentity = UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity);
|
||||
|
||||
Assert.AreEqual("1234", backofficeIdentity.Id);
|
||||
Assert.AreEqual(sessionId, backofficeIdentity.SessionId);
|
||||
Assert.AreEqual(securityStamp, backofficeIdentity.SecurityStamp);
|
||||
Assert.AreEqual("testing", backofficeIdentity.Username);
|
||||
Assert.AreEqual("hello world", backofficeIdentity.RealName);
|
||||
Assert.AreEqual(1, backofficeIdentity.StartContentNodes.Length);
|
||||
@@ -50,7 +53,7 @@ namespace Umbraco.Tests.Security
|
||||
Assert.AreEqual("en-us", backofficeIdentity.Culture);
|
||||
Assert.IsTrue(new[] { "admin" }.SequenceEqual(backofficeIdentity.Roles));
|
||||
|
||||
Assert.AreEqual(10, backofficeIdentity.Claims.Count());
|
||||
Assert.AreEqual(11, backofficeIdentity.Claims.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -93,6 +96,7 @@ namespace Umbraco.Tests.Security
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var userData = new UserData(sessionId)
|
||||
{
|
||||
SecurityStamp = sessionId,
|
||||
AllowedApplications = new[] {"content", "media"},
|
||||
Culture = "en-us",
|
||||
Id = 1234,
|
||||
@@ -113,6 +117,7 @@ namespace Umbraco.Tests.Security
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var userData = new UserData(sessionId)
|
||||
{
|
||||
SecurityStamp = sessionId,
|
||||
AllowedApplications = new[] { "content", "media" },
|
||||
Culture = "en-us",
|
||||
Id = 1234,
|
||||
@@ -139,6 +144,7 @@ namespace Umbraco.Tests.Security
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var userData = new UserData(sessionId)
|
||||
{
|
||||
SecurityStamp = sessionId,
|
||||
AllowedApplications = new[] { "content", "media" },
|
||||
Culture = "en-us",
|
||||
Id = 1234,
|
||||
@@ -162,6 +168,7 @@ namespace Umbraco.Tests.Security
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var userData = new UserData(sessionId)
|
||||
{
|
||||
SecurityStamp = sessionId,
|
||||
AllowedApplications = new[] { "content", "media" },
|
||||
Culture = "en-us",
|
||||
Id = 1234,
|
||||
|
||||
@@ -388,13 +388,12 @@ namespace Umbraco.Tests.TestHelpers
|
||||
return ctx;
|
||||
}
|
||||
|
||||
protected FakeHttpContextFactory GetHttpContextFactory(string url, RouteData routeData = null)
|
||||
protected virtual FakeHttpContextFactory GetHttpContextFactory(string url, RouteData routeData = null)
|
||||
{
|
||||
var factory = routeData != null
|
||||
? new FakeHttpContextFactory(url, routeData)
|
||||
: new FakeHttpContextFactory(url);
|
||||
|
||||
|
||||
//set the state helper
|
||||
StateHelper.HttpContext = factory.HttpContext;
|
||||
|
||||
|
||||
@@ -118,26 +118,13 @@ namespace Umbraco.Tests.TestHelpers
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
protected virtual bool PluginManagerResetRequired
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inheritors can resset the plugin manager if they choose to on teardown
|
||||
/// </summary>
|
||||
protected virtual void ResetPluginManager()
|
||||
{
|
||||
if (PluginManagerResetRequired)
|
||||
{
|
||||
PluginManager.Current = null;
|
||||
}
|
||||
}
|
||||
PluginManager.Current = null;
|
||||
}
|
||||
|
||||
protected virtual CacheHelper CreateCacheHelper()
|
||||
{
|
||||
@@ -185,26 +172,23 @@ namespace Umbraco.Tests.TestHelpers
|
||||
/// </summary>
|
||||
protected virtual void SetupPluginManager()
|
||||
{
|
||||
if (PluginManager.Current == null || PluginManagerResetRequired)
|
||||
PluginManager.Current = new PluginManager(
|
||||
new ActivatorServiceProvider(),
|
||||
CacheHelper.RuntimeCache, ProfilingLogger, false)
|
||||
{
|
||||
PluginManager.Current = new PluginManager(
|
||||
new ActivatorServiceProvider(),
|
||||
CacheHelper.RuntimeCache, ProfilingLogger, false)
|
||||
AssembliesToScan = new[]
|
||||
{
|
||||
AssembliesToScan = new[]
|
||||
{
|
||||
Assembly.Load("Umbraco.Core"),
|
||||
Assembly.Load("umbraco"),
|
||||
Assembly.Load("Umbraco.Tests"),
|
||||
Assembly.Load("businesslogic"),
|
||||
Assembly.Load("cms"),
|
||||
Assembly.Load("controls"),
|
||||
Assembly.Load("umbraco.editorControls"),
|
||||
Assembly.Load("umbraco.MacroEngines"),
|
||||
Assembly.Load("umbraco.providers"),
|
||||
}
|
||||
};
|
||||
}
|
||||
Assembly.Load("Umbraco.Core"),
|
||||
Assembly.Load("umbraco"),
|
||||
Assembly.Load("Umbraco.Tests"),
|
||||
Assembly.Load("businesslogic"),
|
||||
Assembly.Load("cms"),
|
||||
Assembly.Load("controls"),
|
||||
Assembly.Load("umbraco.editorControls"),
|
||||
Assembly.Load("umbraco.MacroEngines"),
|
||||
Assembly.Load("umbraco.providers"),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -24,9 +24,11 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
{
|
||||
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
|
||||
{
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var identity = new UmbracoBackOfficeIdentity(
|
||||
new UserData(Guid.NewGuid().ToString())
|
||||
new UserData(sessionId)
|
||||
{
|
||||
SecurityStamp = sessionId,
|
||||
Id = 0,
|
||||
Roles = new[] { "admin" },
|
||||
AllowedApplications = new[] { "content", "media", "members" },
|
||||
|
||||
@@ -7,12 +7,19 @@ using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Deploy;
|
||||
using Umbraco.Core.Serialization;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
|
||||
namespace Umbraco.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class UdiTests
|
||||
public class UdiTests : BaseUmbracoApplicationTest
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Udi.ResetUdiTypes();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StringUdiCtorTest()
|
||||
{
|
||||
|
||||
@@ -188,6 +188,7 @@
|
||||
<Compile Include="DateTimeExtensionsTests.cs" />
|
||||
<Compile Include="Cache\CacheRefresherEventHandlerTests.cs" />
|
||||
<Compile Include="Models\Mapping\UserModelMapperTests.cs" />
|
||||
<Compile Include="Packaging\PackageExtractionTests.cs" />
|
||||
<Compile Include="Persistence\Repositories\SimilarNodeNameTests.cs" />
|
||||
<Compile Include="TestHelpers\ControllerTesting\AuthenticateEverythingExtensions.cs" />
|
||||
<Compile Include="TestHelpers\ControllerTesting\AuthenticateEverythingMiddleware.cs" />
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user