Compare commits
628 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ee510ed38 | |||
| 136e9e66d2 | |||
| 55a2eb5b6c | |||
| 0c2c12fdd9 | |||
| ec427afe14 | |||
| 155a304ce3 | |||
| 161a19a82b | |||
| e40ed5e858 | |||
| 1ffbd734c8 | |||
| fd5bc49c87 | |||
| 2301a00cd1 | |||
| 313f7967cb | |||
| 6adaa1f70c | |||
| 8960b0828e | |||
| 397ab0e979 | |||
| 15c803b975 | |||
| d62b36ae5b | |||
| 34506664df | |||
| 2557b9a7fb | |||
| ac66dc9892 | |||
| 11e3d8c761 | |||
| 22b9941822 | |||
| b5b17a466b | |||
| 2c5b37a842 | |||
| ae864a3b19 | |||
| 449e464a14 | |||
| 5f72eb74db | |||
| fa85ce39c7 | |||
| bdea519770 | |||
| 78657f34d6 | |||
| e8fd7e2373 | |||
| 3da9fddd64 | |||
| e1821697d4 | |||
| a7756afae6 | |||
| fcd3bb2eee | |||
| cf5dac0ddd | |||
| 1cdf5c042e | |||
| 2281cd8291 | |||
| fa68f7fafe | |||
| 898e39dac5 | |||
| f6045a49b5 | |||
| 849cd4d315 | |||
| 10141bf4f6 | |||
| 11fcc8cd65 | |||
| 95aaf40281 | |||
| e25a39ec5d | |||
| c165a34ed7 | |||
| dc616bb6bf | |||
| cd6c7e51fd | |||
| 2caad3f2d0 | |||
| 4850b01097 | |||
| 92c0cfb9df | |||
| 8c5ee547b1 | |||
| b9c9a232cd | |||
| b565513052 | |||
| cff24e6ccf | |||
| 8ef3ecd714 | |||
| 1f66ebff1f | |||
| 47f8b483a9 | |||
| 1c56cd48ae | |||
| 6e9a9d6444 | |||
| c4850199f7 | |||
| 679333b9c9 | |||
| 02c609747d | |||
| 413e70db7d | |||
| c2cbac7ef9 | |||
| a4a079cd1b | |||
| 22fc9c617c | |||
| 0bcdfc8820 | |||
| b868c06e79 | |||
| f0dd32ea19 | |||
| 97607bc83b | |||
| 5b41262f41 | |||
| e8e1cd8323 | |||
| 1e17d6a527 | |||
| 0d05d0bf34 | |||
| 94184238a2 | |||
| f21d1f879e | |||
| 9c1b2499d5 | |||
| 60a315f964 | |||
| 1870d5444d | |||
| a927d5fb84 | |||
| e433daf5ff | |||
| af476e1536 | |||
| fcfb9afc22 | |||
| 05818f6262 | |||
| accf8ba5f2 | |||
| 1788682af9 | |||
| a41833c137 | |||
| 7abc6d3d4f | |||
| a551881ce2 | |||
| 4a5a8497b4 | |||
| 567c0977d0 | |||
| 9ec4247350 | |||
| 2f31175db5 | |||
| c2722d8120 | |||
| 325cefa349 | |||
| e43ecfd42c | |||
| 76725aa0d8 | |||
| d4cf4cd700 | |||
| 48c0b95707 | |||
| a5caf361e0 | |||
| 74c3aaade5 | |||
| cf94da2b8f | |||
| 826d6d5b15 | |||
| 56f4904fc0 | |||
| 9178e7afcf | |||
| 7f3733d4b5 | |||
| 3df23c3a34 | |||
| 7746c4a014 | |||
| b18945e630 | |||
| 4640cf56d1 | |||
| eae1ce7012 | |||
| e900711180 | |||
| 9f4875c8a7 | |||
| 46902d4335 | |||
| 5eb22b0fd9 | |||
| 6a18965946 | |||
| be1ec05072 | |||
| bb2caf85cb | |||
| 8ee8bda953 | |||
| b42f75df17 | |||
| caa3a3319f | |||
| 5caf95331a | |||
| 040e45ca70 | |||
| bd3d626147 | |||
| 47d08e69a4 | |||
| df182674b4 | |||
| 6ec9a2ae45 | |||
| bf00f7a663 | |||
| 74d8341620 | |||
| 70207e5374 | |||
| f4c038c64c | |||
| c4fa3aa649 | |||
| d51cda7843 | |||
| 372ffee42b | |||
| c1cfea7ebd | |||
| 3cdd8a0c43 | |||
| bf130584a6 | |||
| c3e67705ba | |||
| 7a80d689aa | |||
| eabc009f0c | |||
| d3a2d88347 | |||
| 93f4d65227 | |||
| a4c0342bda | |||
| 6c328d729d | |||
| 095633ba27 | |||
| 633d1bb0ae | |||
| fb89c3da3c | |||
| 3097a46ab3 | |||
| c63156fcd8 | |||
| 29690df567 | |||
| 84a90884e3 | |||
| d29b7311fa | |||
| 17b59cc20f | |||
| 4423571eaa | |||
| 750600b202 | |||
| 28eb4ff03a | |||
| 79517f171c | |||
| e76c42f874 | |||
| 151c35bc47 | |||
| 84265a9716 | |||
| 6618388973 | |||
| 61ce533a08 | |||
| 0a44ad3ab6 | |||
| 7c34255abe | |||
| d52216103b | |||
| a26cd625e7 | |||
| b280bf73aa | |||
| a6d57a5f6d | |||
| 1f22191bbc | |||
| 25088042a9 | |||
| 6b7a83c6c9 | |||
| eb5f2e29a3 | |||
| 02397fd90c | |||
| cf86409e3f | |||
| 134f5e2601 | |||
| e0ded863cc | |||
| bd55594be1 | |||
| 0e82696082 | |||
| 37ac524e8e | |||
| 43af2f8e55 | |||
| a7a6854f9b | |||
| a20584e3de | |||
| 19ec26fef9 | |||
| 23d225c14f | |||
| 527f98189a | |||
| 1a289be999 | |||
| 8a0485e093 | |||
| 647492fbfb | |||
| 78c289a559 | |||
| 646abb3aea | |||
| 2af59471a2 | |||
| fc43d3cdee | |||
| b76b09026b | |||
| 971d7cf933 | |||
| 118a83e56c | |||
| 1f6dea7177 | |||
| f2667c3360 | |||
| f331736384 | |||
| 090f89e5b6 | |||
| a6bc94185c | |||
| 4870afd2d3 | |||
| 80257b6684 | |||
| 8f3a0b6919 | |||
| 9fd2f34343 | |||
| e774717346 | |||
| 5f5759c8a3 | |||
| 0b90097aad | |||
| 58533ca17e | |||
| 427e99dd29 | |||
| 1ec09e15cf | |||
| a48568d1ba | |||
| 5aa1b81cd4 | |||
| 7cd540c37e | |||
| 83595ecaf5 | |||
| e07c5e6ff8 | |||
| 67ff827d67 | |||
| e1208e5019 | |||
| 3f7159797f | |||
| 995f644501 | |||
| f7eec8dafb | |||
| e039265fab | |||
| f08192a4ab | |||
| a16ac1f0ba | |||
| 469d6f9ca3 | |||
| 54cc80f4e8 | |||
| 64628c0a9d | |||
| 6f81b7fe24 | |||
| f2819f61e5 | |||
| 2107e9d9e3 | |||
| c623b448ba | |||
| 1e8bdc8f0c | |||
| 8bb49f57e8 | |||
| da0b1c2f23 | |||
| a288fc62eb | |||
| 82e50ccd3a | |||
| 15b0dc9f29 | |||
| 8057d09fc2 | |||
| 8877100bf0 | |||
| 41c96e6e26 | |||
| abdd117264 | |||
| 6272dac698 | |||
| 7a4b5fa796 | |||
| c962132ee0 | |||
| 0780979469 | |||
| a42e568fdf | |||
| e62661cc6a | |||
| 6841b2ac61 | |||
| f7f4276485 | |||
| bb9e23ca3e | |||
| 10db9363f4 | |||
| 7d5d69e2e3 | |||
| 06897aa8c6 | |||
| ee75158dd0 | |||
| b49db0e941 | |||
| c022397d86 | |||
| e25f6e82ed | |||
| d104525a50 | |||
| b0a64aa4b5 | |||
| ca86385269 | |||
| fb8368267e | |||
| ef4322a9b3 | |||
| 7032e137b8 | |||
| e99643ecfd | |||
| a325529eb8 | |||
| c68f832be6 | |||
| 4ab1c51327 | |||
| 6f39bac32a | |||
| e007a37c36 | |||
| ab9e029525 | |||
| d298388a7b | |||
| 4463abe9bb | |||
| d1db38182f | |||
| e808360653 | |||
| c5df712471 | |||
| 2f67008987 | |||
| 1672e284c5 | |||
| 60ce36a7f2 | |||
| 05c12d1f27 | |||
| 72e2cacc87 | |||
| 4285102422 | |||
| 5ffdf4dd60 | |||
| abbcf0e26b | |||
| 347b25472f | |||
| b745948dc6 | |||
| 8007c8869f | |||
| b51a328ead | |||
| f61437cc7c | |||
| f3b8f2eb5f | |||
| de4628f450 | |||
| b0a64655b4 | |||
| 48d88ec8fa | |||
| 7ca151a3fa | |||
| 363c8fab2c | |||
| 8d4fd9d1cf | |||
| 24071eb3b1 | |||
| 62ffbe7cf0 | |||
| f2d6b47e47 | |||
| f3541908c7 | |||
| 4e57e1cc18 | |||
| 410afffaa9 | |||
| 936e94789d | |||
| 468a56e5de | |||
| 468ec8824a | |||
| cad913b7a1 | |||
| 382103d99d | |||
| cbb6b9bca0 | |||
| a1b90a50cd | |||
| 9ea5855073 | |||
| 4a37433042 | |||
| 91469812d3 | |||
| 68a8eb7f3f | |||
| 2e7baabb94 | |||
| 57745fe71d | |||
| de6f9236e7 | |||
| a00c59e8b5 | |||
| 05c2da807f | |||
| 0fd8ca43d6 | |||
| 87f09b2ee6 | |||
| e466878925 | |||
| 7adf917570 | |||
| 7c41db4bcc | |||
| 5caaef7795 | |||
| 3b72945b1f | |||
| 71eb22d0e8 | |||
| 9d780a2e8f | |||
| 55b00069cb | |||
| b304728f4e | |||
| 319acc199e | |||
| 2a982f21a7 | |||
| 3ff455f11d | |||
| 41ad1bf27b | |||
| f49d939a2d | |||
| c7e80fc532 | |||
| b47d691841 | |||
| 7e1e83b493 | |||
| f6971185f8 | |||
| 244429b972 | |||
| 80ceba36dc | |||
| d33b617851 | |||
| 2b5f370c92 | |||
| 4a0cf81d7d | |||
| fef743517f | |||
| b3cbcebf5b | |||
| 3d90c2b83f | |||
| 3b8b5b6dfa | |||
| 85f95cf067 | |||
| 23be0134e7 | |||
| 355fb64d8b | |||
| 42959a1db5 | |||
| 5b754b3958 | |||
| cba31986fe | |||
| 07b0267668 | |||
| a83291cc6e | |||
| a1cd22208b | |||
| 59d75277c2 | |||
| 69e193e887 | |||
| c3116d5df7 | |||
| b0b874212e | |||
| 19086ab3a1 | |||
| 069594c3ac | |||
| 0bc4c7e2aa | |||
| 7f12cff3bd | |||
| 56adab43d6 | |||
| 83294b6594 | |||
| e0c89eb1ea | |||
| 9ad8e6a876 | |||
| 14b58386e5 | |||
| e26cbccfe0 | |||
| 7dc06c1bea | |||
| effa984bdc | |||
| fa058f348d | |||
| 62d9a1a132 | |||
| 9284269428 | |||
| f8c4481eb1 | |||
| d7f2041ee9 | |||
| 9f6e6e4701 | |||
| 34ba3f3351 | |||
| dcfb8b94d8 | |||
| 0876004d9a | |||
| d3ad61835a | |||
| ad882ab5be | |||
| 774f333c70 | |||
| 700b71f9fb | |||
| bba58ea0c0 | |||
| d0d4885812 | |||
| ce11a377e6 | |||
| c6443c477f | |||
| eb94572361 | |||
| fd8317493e | |||
| 3642ff72a4 | |||
| 6e830c1171 | |||
| 22ff1bcaa0 | |||
| ab523baf4f | |||
| 2dec03f441 | |||
| 9ada0d9c97 | |||
| c3be9004f6 | |||
| 11fa015b64 | |||
| df553140e8 | |||
| 0b898f2e32 | |||
| 4c9be2cc49 | |||
| 1aad4e50d5 | |||
| f5f3676a56 | |||
| f69f68e2b5 | |||
| 3858656015 | |||
| a5ec5f46ac | |||
| ea70e18a91 | |||
| 0cfd5773c3 | |||
| b15def99c7 | |||
| 8c8b6984c2 | |||
| bc828fc6f3 | |||
| dc7b83ca6c | |||
| 8188e4ff55 | |||
| fcf9dcd803 | |||
| 070fa11f8c | |||
| f52a84937b | |||
| 7e9f55f517 | |||
| 3556043b55 | |||
| 3e8fb11abd | |||
| 5181988a41 | |||
| 5169310258 | |||
| f987640462 | |||
| 55e97dad74 | |||
| 8c70578764 | |||
| 5cb2dd0036 | |||
| a9713dc6fc | |||
| b32e7d4ccc | |||
| 8d4958e395 | |||
| d2f3e162f1 | |||
| 9883757704 | |||
| 7404e20a19 | |||
| 0310782d7d | |||
| 744ff0cf33 | |||
| afd0018dd6 | |||
| c36aec624b | |||
| c81e14758d | |||
| 1ba86f361a | |||
| 9b33c94437 | |||
| 2a30fd238e | |||
| 3cb4128c0f | |||
| 79958e0fe0 | |||
| f238f9fcea | |||
| 7c545410cc | |||
| e5a21625ea | |||
| dcf5fff3f0 | |||
| 1dc88658e8 | |||
| efd3d6937a | |||
| e17f75a18a | |||
| 6d46c2204d | |||
| 27b9385df5 | |||
| 88c20c7c5f | |||
| bd9adffaa5 | |||
| ea6d81f7ac | |||
| 0ce059d5bd | |||
| 773ef1d168 | |||
| 1f7de81cd2 | |||
| fef9e3fde0 | |||
| e849d2d0d0 | |||
| 9a6123579f | |||
| f56bd731b7 | |||
| bee0642417 | |||
| 43abb08e59 | |||
| 17db5709f1 | |||
| 2fc6d9b7df | |||
| 591a881283 | |||
| e4561851a4 | |||
| c6daf2e111 | |||
| 1554d52ea9 | |||
| 883366f741 | |||
| cac331adbc | |||
| 18318ecabf | |||
| 6b9ef6df7a | |||
| fcaf78ef29 | |||
| 833b4c552c | |||
| c678b7f6d9 | |||
| e320840a29 | |||
| b664fc5e94 | |||
| b87346bee5 | |||
| 6029306fe9 | |||
| f979792290 | |||
| 5f82fc6e20 | |||
| b1a0ca98ea | |||
| f4f99c2922 | |||
| 6f55b84c65 | |||
| 43f369f96f | |||
| 60924d93fb | |||
| c1b3da869d | |||
| e39158d5e5 | |||
| aed476de0a | |||
| ddd9c8ef7d | |||
| 9e2c7d16a6 | |||
| 44db4e7a48 | |||
| 680d97c630 | |||
| 89347b359b | |||
| 8053cea92d | |||
| f44b158c15 | |||
| 1ff66d0c3a | |||
| ae07f34215 | |||
| aba2c2a79e | |||
| 9f62dfc087 | |||
| a21912dc41 | |||
| 733b4a770f | |||
| 2e097b7e46 | |||
| e1b6997ef7 | |||
| b6b7b9f433 | |||
| 8181f88cb0 | |||
| 3a4865ef7d | |||
| 37f6dbfa6b | |||
| 131fea3ba4 | |||
| b0661218a0 | |||
| 50335723b0 | |||
| 6f1bd3fd71 | |||
| fb99b101d8 | |||
| 91e658c41c | |||
| 0ce7efc978 | |||
| 3b8a2d7d65 | |||
| 61b9ab5c7b | |||
| cfda04b6ed | |||
| 11bdfbba19 | |||
| 9694831638 | |||
| a20844b55b | |||
| 158e40d7f6 | |||
| 6939cc757e | |||
| 2baec8a129 | |||
| 93a1fc1eba | |||
| fe67a09ccb | |||
| 1c4e1db054 | |||
| 5d694a73a3 | |||
| af369fa6c7 | |||
| 901036535d | |||
| 5d6cbee196 | |||
| 9e9ae09ec2 | |||
| 58697eaa04 | |||
| ce58ad91b5 | |||
| e49d14d687 | |||
| 3c98d9d2e1 | |||
| e010ff2a8c | |||
| 8d14a3ef2c | |||
| 2f3d9c9509 | |||
| 6e8f3b6e13 | |||
| b9cbdbd9a7 | |||
| ebf35356a8 | |||
| 00cec3267c | |||
| 4bfb2d219b | |||
| f3ba1fa7ed | |||
| 7eca99e249 | |||
| 80c61110df | |||
| 837bb83d48 | |||
| 054ac78970 | |||
| 8e47f91ba5 | |||
| 31f6bd4c02 | |||
| 3e5744dbb7 | |||
| 5982a868ea | |||
| 69a42e26a9 | |||
| d9ed420ed4 | |||
| 2b3397120f | |||
| cb0d8320e6 | |||
| 53d9b65e64 | |||
| df0fe62fca | |||
| 1f41aa7d81 | |||
| dad77fc506 | |||
| 350f5c88c7 | |||
| 928c0dc535 | |||
| 483a2c7c02 | |||
| cc7a28db65 | |||
| 065e764957 | |||
| 000ba73887 | |||
| 084fde885a | |||
| 08b81d991b | |||
| 4937f87c0d | |||
| 50a5679b5c | |||
| 2c248791e1 | |||
| 6b6a7fe231 | |||
| 6fd197d1bf | |||
| 00aa59c9c6 | |||
| 06254ba462 | |||
| 684cb19a7c | |||
| 9c5da12653 | |||
| 83f229abc6 | |||
| 3405a1f600 | |||
| 8be02ff7b0 | |||
| c371f00315 | |||
| 0ccf798f14 | |||
| 6ca94074f3 | |||
| 8236eee7a6 | |||
| 8bdeca6374 | |||
| e37bbdfcf4 | |||
| dac882ce9f | |||
| 5678be0db9 | |||
| dc67d82671 | |||
| c7d9683fc4 | |||
| ed7675bdcd | |||
| 3c7c6b4ad7 | |||
| 838d7975c9 | |||
| 5af9712d51 | |||
| e14aba3875 | |||
| 579c572dd8 | |||
| bbf4c18825 | |||
| 77162e4b51 | |||
| e72758e04a | |||
| 9d373c194d | |||
| 856e123ec2 | |||
| 0b2af6b48f | |||
| 9260f943ff | |||
| dcdaa8d62d | |||
| 360665c082 | |||
| ff98b5f321 | |||
| 0df7767f4a | |||
| 92378095a2 | |||
| daa0741802 | |||
| eaa8ca5fdc | |||
| 73b2d31298 | |||
| 8676a99b3a | |||
| 0b2cfe956d | |||
| e0e9823a03 | |||
| 464a227af1 | |||
| 3a98d63bf1 | |||
| ac65af772a | |||
| 6c172702be | |||
| 64f477f90e | |||
| 813741bc3d | |||
| 7566546fd7 | |||
| a4f83e0125 | |||
| 59b8b397b7 | |||
| 7c72e70967 | |||
| 75b74a6687 | |||
| 7dcb4435d8 |
+11
-5
@@ -1,10 +1,16 @@
|
||||
root=true
|
||||
# editorconfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Default settings:
|
||||
# A newline ending every file
|
||||
# Use 4 spaces as indentation
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{cs,cshtml,csx,vb,vbx,vbhtml,fs,fsx,txt,ps1,sql}]
|
||||
indent_size = 4
|
||||
# Trim trailing whitespace, limited support.
|
||||
# https://github.com/editorconfig/editorconfig/wiki/Property-research:-Trim-trailing-spaces
|
||||
trim_trailing_whitespace = true
|
||||
@@ -0,0 +1,49 @@
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.gif binary
|
||||
|
||||
*.cs text=auto diff=csharp
|
||||
*.vb text=auto
|
||||
*.c text=auto
|
||||
*.cpp text=auto
|
||||
*.cxx text=auto
|
||||
*.h text=auto
|
||||
*.hxx text=auto
|
||||
*.py text=auto
|
||||
*.rb text=auto
|
||||
*.java text=auto
|
||||
*.html text=auto
|
||||
*.htm text=auto
|
||||
*.css text=auto
|
||||
*.scss text=auto
|
||||
*.sass text=auto
|
||||
*.less text=auto
|
||||
*.js text=auto
|
||||
*.lisp text=auto
|
||||
*.clj text=auto
|
||||
*.sql text=auto
|
||||
*.php text=auto
|
||||
*.lua text=auto
|
||||
*.m text=auto
|
||||
*.asm text=auto
|
||||
*.erl text=auto
|
||||
*.fs text=auto
|
||||
*.fsx text=auto
|
||||
*.hs text=auto
|
||||
|
||||
*.csproj text=auto merge=union
|
||||
*.vbproj text=auto merge=union
|
||||
*.fsproj text=auto merge=union
|
||||
*.dbproj text=auto merge=union
|
||||
*.sln text=auto eol=crlf merge=union
|
||||
+2
-1
@@ -139,4 +139,5 @@ src/PrecompiledWeb/*
|
||||
|
||||
build.out/
|
||||
build.tmp/
|
||||
build/Modules/*/temp/
|
||||
build/Modules/*/temp/
|
||||
/src/.idea/*
|
||||
|
||||
@@ -44,7 +44,7 @@ function Build-UmbracoDocs
|
||||
# change baseUrl
|
||||
$baseUrl = "https://our.umbraco.org/apidocs/ui/"
|
||||
$indexPath = "$src/Umbraco.Web.UI.Client/docs/api/index.html"
|
||||
(Get-Content $indexPath).Replace("location.href.replace(rUrl, indexFile)", "'$baseUrl'") `
|
||||
(Get-Content $indexPath).Replace("origin + location.href.substr(origin.length).replace(rUrl, indexFile)", "'$baseUrl'") `
|
||||
| Set-Content $indexPath
|
||||
|
||||
# restore
|
||||
@@ -100,8 +100,9 @@ function Get-DocFx($uenv, $buildTemp)
|
||||
$docFx = "$buildTemp\docfx"
|
||||
if (-not (test-path $docFx))
|
||||
{
|
||||
Write-Host "Download DocFx..."
|
||||
$source = "https://github.com/dotnet/docfx/releases/download/v2.19.2/docfx.zip"
|
||||
Write-Host "Download DocFx from $source"
|
||||
|
||||
Invoke-WebRequest $source -OutFile "$buildTemp\docfx.zip"
|
||||
|
||||
&$uenv.Zip x "$buildTemp\docfx.zip" -o"$buildTemp\docfx" -aos > $nul
|
||||
|
||||
@@ -239,18 +239,18 @@ function Prepare-Tests
|
||||
|
||||
# data
|
||||
Write-Host "Copy data files"
|
||||
if( -Not (Test-Path -Path "$tmp\tests\Packaging" ) )
|
||||
if (-Not (Test-Path -Path "$tmp\tests\Packaging" ) )
|
||||
{
|
||||
Write-Host "Create packaging directory"
|
||||
New-Item -ItemType directory -Path "$tmp\tests\Packaging"
|
||||
mkdir "$tmp\tests\Packaging" > $null
|
||||
}
|
||||
Copy-Files "$src\Umbraco.Tests\Packaging\Packages" "*" "$tmp\tests\Packaging\Packages"
|
||||
|
||||
# required for package install tests
|
||||
if( -Not (Test-Path -Path "$tmp\tests\bin" ) )
|
||||
if (-Not (Test-Path -Path "$tmp\tests\bin" ) )
|
||||
{
|
||||
Write-Host "Create bin directory"
|
||||
New-Item -ItemType directory -Path "$tmp\tests\bin"
|
||||
mkdir "$tmp\tests\bin" > $null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,17 +28,17 @@
|
||||
<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="MySql.Data" version="[6.9.9, 7.0.0)" />
|
||||
<dependency id="ClientDependency" version="[1.9.2, 2.0.0)" />
|
||||
<dependency id="ClientDependency" version="[1.9.6, 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="Examine" version="[0.1.89, 1.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)" />
|
||||
<dependency id="System.Threading.Tasks.Dataflow" version="[4.7.0, 5.0.0)" />
|
||||
<dependency id="System.Threading.Tasks.Dataflow" version="[4.7.0, 5.0.0)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<dependencies>
|
||||
<dependency id="UmbracoCms.Core" version="[$version$]" />
|
||||
<dependency id="Newtonsoft.Json" version="[10.0.2, 11.0.0)" />
|
||||
<dependency id="Umbraco.ModelsBuilder" version="[3.0.7, 4.0.0)" />
|
||||
<dependency id="Umbraco.ModelsBuilder" version="[3.0.10, 4.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.2.1, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web.Config" version="[2.3.1, 3.0.0)" />
|
||||
</dependencies>
|
||||
@@ -32,6 +32,7 @@
|
||||
<file src="$BuildTmp$\WebApp\bin\amd64\**" target="UmbracoFiles\bin\amd64" />
|
||||
<file src="$BuildTmp$\WebApp\bin\x86\**" target="UmbracoFiles\bin\x86" />
|
||||
<file src="$BuildTmp$\WebApp\config\splashes\**" target="UmbracoFiles\Config\splashes" />
|
||||
<file src="$BuildTmp$\WebApp\config\BackOfficeTours\**" target="Content\Config\BackOfficeTours" />
|
||||
<file src="$BuildTmp$\WebApp\umbraco\**" target="UmbracoFiles\umbraco" />
|
||||
<file src="$BuildTmp$\WebApp\umbraco_client\**" target="UmbracoFiles\umbraco_client" />
|
||||
<file src="$BuildTmp$\WebApp\Media\Web.config" target="Content\Media\Web.config" />
|
||||
|
||||
@@ -98,10 +98,51 @@ if ($project) {
|
||||
$umbracoUIXMLDestination = Join-Path $projectPath "Umbraco\Config\Create\UI.xml"
|
||||
Copy-Item $umbracoUIXMLSource $umbracoUIXMLDestination -Force
|
||||
} else {
|
||||
# This part only runs for upgrades
|
||||
|
||||
$upgradeViewSource = Join-Path $umbracoFolderSource "Views\install\*"
|
||||
$upgradeView = Join-Path $umbracoFolder "Views\install\"
|
||||
Write-Host "Copying2 ${upgradeViewSource} to ${upgradeView}"
|
||||
Copy-Item $upgradeViewSource $upgradeView -Force
|
||||
|
||||
Try
|
||||
{
|
||||
# Disable tours for upgrades, presumably Umbraco experience is already available
|
||||
$umbracoSettingsConfigPath = Join-Path $configFolder "umbracoSettings.config"
|
||||
$content = (Get-Content $umbracoSettingsConfigPath).Replace('<tours enable="true">','<tours enable="false">')
|
||||
# Saves with UTF-8 encoding without BOM which makes sure Umbraco can still read it
|
||||
# Reference: https://stackoverflow.com/a/32951824/5018
|
||||
[IO.File]::WriteAllLines($umbracoSettingsConfigPath, $content)
|
||||
}
|
||||
Catch
|
||||
{
|
||||
# Not a big problem if this fails, let it go
|
||||
}
|
||||
|
||||
Try
|
||||
{
|
||||
$uiXmlConfigPath = Join-Path $umbracoFolder -ChildPath "Config" | Join-Path -ChildPath "create" | Join-Path -ChildPath "UI.xml"
|
||||
$uiXmlFile = Join-Path $umbracoFolder -ChildPath "Config" | Join-Path -ChildPath "create" | Join-Path -ChildPath "UI.xml"
|
||||
|
||||
$uiXml = New-Object System.Xml.XmlDocument
|
||||
$uiXml.PreserveWhitespace = $true
|
||||
|
||||
$uiXml.Load($uiXmlFile)
|
||||
$createExists = $uiXml.SelectNodes("//nodeType[@alias='macros']/tasks/create")
|
||||
|
||||
if($createExists.Count -eq 0)
|
||||
{
|
||||
$macrosTasksNode = $uiXml.SelectNodes("//nodeType[@alias='macros']/tasks")
|
||||
|
||||
#Creating: <create assembly="umbraco" type="macroTasks" />
|
||||
$createNode = $uiXml.CreateElement("create")
|
||||
$createNode.SetAttribute("assembly", "umbraco")
|
||||
$createNode.SetAttribute("type", "macroTasks")
|
||||
$macrosTasksNode.AppendChild($createNode)
|
||||
$uiXml.Save($uiXmlFile)
|
||||
}
|
||||
}
|
||||
Catch { }
|
||||
}
|
||||
|
||||
$installFolder = Join-Path $projectPath "Install"
|
||||
|
||||
@@ -79,14 +79,14 @@
|
||||
<add sortOrder="1" alias="dataTypes" application="developer"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes(sortOrder)" />
|
||||
|
||||
<add application="developer" alias="macros" title="Macros" type="umbraco.loadMacros, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="2"
|
||||
|
||||
<add initialize="true" sortOrder="2" alias="macros" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MacroTreeController, umbraco"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes()" />
|
||||
<add application="developer" alias="relationTypes" title="Relation Types" type="umbraco.loadRelationTypes, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes()" />
|
||||
<add application="developer" alias="xslt" title="XSLT Files" type="umbraco.loadXslt, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="5"
|
||||
<add initialize="true" sortOrder="5" alias="xslt" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.XsltTreeController, umbraco"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes()" />
|
||||
|
||||
|
||||
+1
-1
@@ -2,6 +2,6 @@
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://www.nuget.org/api/v2/" />
|
||||
<add key="umbracocore" value="http://www.myget.org/f/umbracocore/" />
|
||||
<add key="umbracocore" value="https://www.myget.org/F/umbracocore/api/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
@@ -49,11 +49,9 @@
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Data.SqlServerCe.Entity, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
|
||||
+2
-2
@@ -11,5 +11,5 @@ using System.Resources;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
[assembly: AssemblyFileVersion("7.8.0")]
|
||||
[assembly: AssemblyInformationalVersion("7.8.0-beta")]
|
||||
[assembly: AssemblyFileVersion("7.9.0")]
|
||||
[assembly: AssemblyInformationalVersion("7.9.0")]
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Threading;
|
||||
using Semver;
|
||||
@@ -261,7 +262,7 @@ namespace Umbraco.Core
|
||||
/// - http://issues.umbraco.org/issue/U4-5728
|
||||
/// - http://issues.umbraco.org/issue/U4-5391
|
||||
/// </remarks>
|
||||
internal string UmbracoApplicationUrl
|
||||
public string UmbracoApplicationUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -269,9 +270,21 @@ namespace Umbraco.Core
|
||||
return _umbracoApplicationUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the url.
|
||||
/// </summary>
|
||||
public void ResetUmbracoApplicationUrl()
|
||||
{
|
||||
_umbracoApplicationUrl = null;
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
internal string _umbracoApplicationUrl;
|
||||
internal string _umbracoApplicationUrl;
|
||||
|
||||
internal List<string> _umbracoApplicationDomains = new List<string>();
|
||||
|
||||
internal string _umbracoApplicationDeploymentId;
|
||||
|
||||
private Lazy<bool> _configured;
|
||||
internal MainDom MainDom { get; private set; }
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Auditing
|
||||
{
|
||||
public sealed class AuditEventHandler : ApplicationEventHandler
|
||||
{
|
||||
private IAuditService _auditServiceInstance;
|
||||
private IUserService _userServiceInstance;
|
||||
private IEntityService _entityServiceInstance;
|
||||
|
||||
private IUser CurrentPerformingUser
|
||||
{
|
||||
get
|
||||
{
|
||||
var identity = Thread.CurrentPrincipal?.GetUmbracoIdentity();
|
||||
return identity == null
|
||||
? new User { Id = 0, Name = "SYSTEM", Email = "" }
|
||||
: _userServiceInstance.GetUserById(Convert.ToInt32(identity.Id));
|
||||
}
|
||||
}
|
||||
|
||||
private IUser GetPerformingUser(int userId)
|
||||
{
|
||||
var found = userId >= 0 ? _userServiceInstance.GetUserById(userId) : null;
|
||||
return found ?? new User {Id = 0, Name = "SYSTEM", Email = ""};
|
||||
}
|
||||
|
||||
private string PerformingIp
|
||||
{
|
||||
get
|
||||
{
|
||||
var httpContext = HttpContext.Current == null ? (HttpContextBase) null : new HttpContextWrapper(HttpContext.Current);
|
||||
var ip = httpContext.GetCurrentRequestIpAddress();
|
||||
if (ip.ToLowerInvariant().StartsWith("unknown")) ip = "";
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
|
||||
{
|
||||
_auditServiceInstance = applicationContext.Services.AuditService;
|
||||
_userServiceInstance = applicationContext.Services.UserService;
|
||||
_entityServiceInstance = applicationContext.Services.EntityService;
|
||||
|
||||
//BackOfficeUserManager.AccountLocked += ;
|
||||
//BackOfficeUserManager.AccountUnlocked += ;
|
||||
BackOfficeUserManager.ForgotPasswordRequested += OnForgotPasswordRequest;
|
||||
BackOfficeUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange;
|
||||
BackOfficeUserManager.LoginFailed += OnLoginFailed;
|
||||
//BackOfficeUserManager.LoginRequiresVerification += ;
|
||||
BackOfficeUserManager.LoginSuccess += OnLoginSuccess;
|
||||
BackOfficeUserManager.LogoutSuccess += OnLogoutSuccess;
|
||||
BackOfficeUserManager.PasswordChanged += OnPasswordChanged;
|
||||
BackOfficeUserManager.PasswordReset += OnPasswordReset;
|
||||
//BackOfficeUserManager.ResetAccessFailedCount += ;
|
||||
|
||||
UserService.SavedUserGroup2 += OnSavedUserGroupWithUsers;
|
||||
|
||||
UserService.SavedUser += OnSavedUser;
|
||||
UserService.DeletedUser += OnDeletedUser;
|
||||
UserService.UserGroupPermissionsAssigned += UserGroupPermissionAssigned;
|
||||
|
||||
MemberService.Saved += OnSavedMember;
|
||||
MemberService.Deleted += OnDeletedMember;
|
||||
MemberService.AssignedRoles += OnAssignedRoles;
|
||||
MemberService.RemovedRoles += OnRemovedRoles;
|
||||
MemberService.Exported += OnMemberExported;
|
||||
}
|
||||
|
||||
private string FormatEmail(IMember member)
|
||||
{
|
||||
return member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? "" : $"<{member.Email}>";
|
||||
}
|
||||
|
||||
private string FormatEmail(IUser user)
|
||||
{
|
||||
return user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>";
|
||||
}
|
||||
|
||||
private void OnRemovedRoles(IMemberService sender, RolesEventArgs args)
|
||||
{
|
||||
var performingUser = CurrentPerformingUser;
|
||||
var roles = string.Join(", ", args.Roles);
|
||||
var members = sender.GetAllMembers(args.MemberIds).ToDictionary(x => x.Id, x => x);
|
||||
foreach (var id in args.MemberIds)
|
||||
{
|
||||
members.TryGetValue(id, out var member);
|
||||
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
|
||||
DateTime.UtcNow,
|
||||
-1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}",
|
||||
"umbraco/member/roles/removed", $"roles modified, removed {roles}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAssignedRoles(IMemberService sender, RolesEventArgs args)
|
||||
{
|
||||
var performingUser = CurrentPerformingUser;
|
||||
var roles = string.Join(", ", args.Roles);
|
||||
var members = sender.GetAllMembers(args.MemberIds).ToDictionary(x => x.Id, x => x);
|
||||
foreach (var id in args.MemberIds)
|
||||
{
|
||||
members.TryGetValue(id, out var member);
|
||||
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
|
||||
DateTime.UtcNow,
|
||||
-1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}",
|
||||
"umbraco/member/roles/assigned", $"roles modified, assigned {roles}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMemberExported(IMemberService sender, ExportedMemberEventArgs exportedMemberEventArgs)
|
||||
{
|
||||
var performingUser = CurrentPerformingUser;
|
||||
var member = exportedMemberEventArgs.Member;
|
||||
|
||||
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
|
||||
DateTime.UtcNow,
|
||||
-1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}",
|
||||
"umbraco/member/exported", "exported member data");
|
||||
}
|
||||
|
||||
private void OnSavedUserGroupWithUsers(IUserService sender, SaveEventArgs<UserGroupWithUsers> saveEventArgs)
|
||||
{
|
||||
var performingUser = CurrentPerformingUser;
|
||||
foreach (var groupWithUser in saveEventArgs.SavedEntities)
|
||||
{
|
||||
var group = groupWithUser.UserGroup;
|
||||
|
||||
var dp = string.Join(", ", ((UserGroup)group).GetPreviouslyDirtyProperties());
|
||||
var sections = ((UserGroup)group).WasPropertyDirty("AllowedSections")
|
||||
? string.Join(", ", group.AllowedSections)
|
||||
: null;
|
||||
var perms = ((UserGroup)group).WasPropertyDirty("Permissions")
|
||||
? string.Join(", ", group.Permissions)
|
||||
: null;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append($"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)};");
|
||||
if (sections != null)
|
||||
sb.Append($", assigned sections: {sections}");
|
||||
if (perms != null)
|
||||
{
|
||||
if (sections != null)
|
||||
sb.Append(", ");
|
||||
sb.Append($"default perms: {perms}");
|
||||
}
|
||||
|
||||
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
|
||||
DateTime.UtcNow,
|
||||
-1, $"User Group {group.Id} \"{group.Name}\" ({group.Alias})",
|
||||
"umbraco/user-group/save", $"{sb}");
|
||||
|
||||
// now audit the users that have changed
|
||||
|
||||
foreach (var user in groupWithUser.RemovedUsers)
|
||||
{
|
||||
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
|
||||
DateTime.UtcNow,
|
||||
user.Id, $"User \"{user.Name}\" {FormatEmail(user)}",
|
||||
"umbraco/user-group/save", $"Removed user \"{user.Name}\" {FormatEmail(user)} from group {group.Id} \"{group.Name}\" ({group.Alias})");
|
||||
}
|
||||
|
||||
foreach (var user in groupWithUser.AddedUsers)
|
||||
{
|
||||
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
|
||||
DateTime.UtcNow,
|
||||
user.Id, $"User \"{user.Name}\" {FormatEmail(user)}",
|
||||
"umbraco/user-group/save", $"Added user \"{user.Name}\" {FormatEmail(user)} to group {group.Id} \"{group.Name}\" ({group.Alias})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UserGroupPermissionAssigned(IUserService sender, SaveEventArgs<EntityPermission> saveEventArgs)
|
||||
{
|
||||
var performingUser = CurrentPerformingUser;
|
||||
var perms = saveEventArgs.SavedEntities;
|
||||
foreach (var perm in perms)
|
||||
{
|
||||
var group = sender.GetUserGroupById(perm.UserGroupId);
|
||||
var assigned = string.Join(", ", perm.AssignedPermissions);
|
||||
var entity = _entityServiceInstance.Get(perm.EntityId);
|
||||
|
||||
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
|
||||
DateTime.UtcNow,
|
||||
-1, $"User Group {group.Id} \"{group.Name}\" ({group.Alias})",
|
||||
"umbraco/user-group/permissions-change", $"assigning {(string.IsNullOrWhiteSpace(assigned) ? "(nothing)" : assigned)} on id:{perm.EntityId} \"{entity.Name}\"");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSavedMember(IMemberService sender, SaveEventArgs<IMember> saveEventArgs)
|
||||
{
|
||||
var performingUser = CurrentPerformingUser;
|
||||
var members = saveEventArgs.SavedEntities;
|
||||
foreach (var member in members)
|
||||
{
|
||||
var dp = string.Join(", ", ((Member) member).GetPreviouslyDirtyProperties());
|
||||
|
||||
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
|
||||
DateTime.UtcNow,
|
||||
-1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}",
|
||||
"umbraco/member/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDeletedMember(IMemberService sender, DeleteEventArgs<IMember> deleteEventArgs)
|
||||
{
|
||||
var performingUser = CurrentPerformingUser;
|
||||
var members = deleteEventArgs.DeletedEntities;
|
||||
foreach (var member in members)
|
||||
{
|
||||
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
|
||||
DateTime.UtcNow,
|
||||
-1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}",
|
||||
"umbraco/member/delete", $"delete member id:{member.Id} \"{member.Name}\" {FormatEmail(member)}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSavedUser(IUserService sender, SaveEventArgs<IUser> saveEventArgs)
|
||||
{
|
||||
var performingUser = CurrentPerformingUser;
|
||||
var affectedUsers = saveEventArgs.SavedEntities;
|
||||
foreach (var affectedUser in affectedUsers)
|
||||
{
|
||||
var groups = affectedUser.WasPropertyDirty("Groups")
|
||||
? string.Join(", ", affectedUser.Groups.Select(x => x.Alias))
|
||||
: null;
|
||||
|
||||
var dp = string.Join(", ", ((User)affectedUser).GetPreviouslyDirtyProperties());
|
||||
|
||||
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
|
||||
DateTime.UtcNow,
|
||||
affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}",
|
||||
"umbraco/user/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}{(groups == null ? "" : "; groups assigned: " + groups)}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDeletedUser(IUserService sender, DeleteEventArgs<IUser> deleteEventArgs)
|
||||
{
|
||||
var performingUser = CurrentPerformingUser;
|
||||
var affectedUsers = deleteEventArgs.DeletedEntities;
|
||||
foreach (var affectedUser in affectedUsers)
|
||||
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
|
||||
DateTime.UtcNow,
|
||||
affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}",
|
||||
"umbraco/user/delete", "delete user");
|
||||
}
|
||||
|
||||
private void OnLoginSuccess(object sender, EventArgs args)
|
||||
{
|
||||
if (args is IdentityAuditEventArgs identityArgs)
|
||||
{
|
||||
var performingUser = GetPerformingUser(identityArgs.PerformingUser);
|
||||
WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/login", "login success");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLogoutSuccess(object sender, EventArgs args)
|
||||
{
|
||||
if (args is IdentityAuditEventArgs identityArgs)
|
||||
{
|
||||
var performingUser = GetPerformingUser(identityArgs.PerformingUser);
|
||||
WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/logout", "logout success");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPasswordReset(object sender, EventArgs args)
|
||||
{
|
||||
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
|
||||
{
|
||||
WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/reset", "password reset");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPasswordChanged(object sender, EventArgs args)
|
||||
{
|
||||
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
|
||||
{
|
||||
WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/change", "password change");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoginFailed(object sender, EventArgs args)
|
||||
{
|
||||
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
|
||||
{
|
||||
WriteAudit(identityArgs.PerformingUser, 0, identityArgs.IpAddress, "umbraco/user/sign-in/failed", "login failed", affectedDetails: "");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnForgotPasswordChange(object sender, EventArgs args)
|
||||
{
|
||||
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
|
||||
{
|
||||
WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnForgotPasswordRequest(object sender, EventArgs args)
|
||||
{
|
||||
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
|
||||
{
|
||||
WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/request", "password forgot/request");
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteAudit(int performingId, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null)
|
||||
{
|
||||
var performingUser = _userServiceInstance.GetUserById(performingId);
|
||||
|
||||
var performingDetails = performingUser == null
|
||||
? $"User UNKNOWN:{performingId}"
|
||||
: $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}";
|
||||
|
||||
WriteAudit(performingId, performingDetails, affectedId, ipAddress, eventType, eventDetails, affectedDetails);
|
||||
}
|
||||
|
||||
private void WriteAudit(IUser performingUser, int affectedId, string ipAddress, string eventType, string eventDetails)
|
||||
{
|
||||
var performingDetails = performingUser == null
|
||||
? $"User UNKNOWN"
|
||||
: $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}";
|
||||
|
||||
WriteAudit(performingUser?.Id ?? 0, performingDetails, affectedId, ipAddress, eventType, eventDetails);
|
||||
}
|
||||
|
||||
private void WriteAudit(int performingId, string performingDetails, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null)
|
||||
{
|
||||
if (affectedDetails == null)
|
||||
{
|
||||
var affectedUser = _userServiceInstance.GetUserById(affectedId);
|
||||
affectedDetails = affectedUser == null
|
||||
? $"User UNKNOWN:{affectedId}"
|
||||
: $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}";
|
||||
}
|
||||
|
||||
_auditServiceInstance.Write(performingId, performingDetails,
|
||||
ipAddress,
|
||||
DateTime.UtcNow,
|
||||
affectedId, affectedDetails,
|
||||
eventType, eventDetails);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Security;
|
||||
@@ -45,12 +46,8 @@ namespace Umbraco.Core.Auditing
|
||||
/// </summary>
|
||||
public string Username { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the properties on the event being raised, all parameters are optional except for the action being performed
|
||||
/// </summary>
|
||||
/// <param name="action">An action based on the AuditEvent enum</param>
|
||||
/// <param name="ipAddress">The client's IP address. This is usually automatically set but could be overridden if necessary</param>
|
||||
/// <param name="performingUser">The Id of the user performing the action (if different from the user affected by the action)</param>
|
||||
[Obsolete("Use the method that has the affectedUser parameter instead")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public IdentityAuditEventArgs(AuditEvent action, string ipAddress, int performingUser = -1)
|
||||
{
|
||||
DateTimeUtc = DateTime.UtcNow;
|
||||
@@ -63,6 +60,35 @@ namespace Umbraco.Core.Auditing
|
||||
: performingUser;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="ipAddress"></param>
|
||||
/// <param name="comment"></param>
|
||||
/// <param name="performingUser"></param>
|
||||
/// <param name="affectedUser"></param>
|
||||
public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string comment = null, int performingUser = -1, int affectedUser = -1)
|
||||
{
|
||||
DateTimeUtc = DateTime.UtcNow;
|
||||
Action = action;
|
||||
|
||||
IpAddress = ipAddress;
|
||||
Comment = comment;
|
||||
AffectedUser = affectedUser;
|
||||
|
||||
PerformingUser = performingUser == -1
|
||||
? GetCurrentRequestBackofficeUserId()
|
||||
: performingUser;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance without a performing or affected user (the id will be set to -1)
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="ipAddress"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="comment"></param>
|
||||
public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string username, string comment)
|
||||
{
|
||||
DateTimeUtc = DateTime.UtcNow;
|
||||
@@ -71,6 +97,22 @@ namespace Umbraco.Core.Auditing
|
||||
IpAddress = ipAddress;
|
||||
Username = username;
|
||||
Comment = comment;
|
||||
|
||||
PerformingUser = -1;
|
||||
}
|
||||
|
||||
public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string username, string comment, int performingUser)
|
||||
{
|
||||
DateTimeUtc = DateTime.UtcNow;
|
||||
Action = action;
|
||||
|
||||
IpAddress = ipAddress;
|
||||
Username = username;
|
||||
Comment = comment;
|
||||
|
||||
PerformingUser = performingUser == -1
|
||||
? GetCurrentRequestBackofficeUserId()
|
||||
: performingUser;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace Umbraco.Core.Cache
|
||||
#region Insert
|
||||
#endregion
|
||||
|
||||
private class NoopLocker : DisposableObject
|
||||
private class NoopLocker : DisposableObjectSlim
|
||||
{
|
||||
protected override void DisposeResources()
|
||||
{ }
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a composite key of (Type, Type) for fast dictionaries.
|
||||
/// </summary>
|
||||
internal struct CompositeTypeTypeKey : IEquatable<CompositeTypeTypeKey>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CompositeTypeTypeKey"/> struct.
|
||||
/// </summary>
|
||||
public CompositeTypeTypeKey(Type type1, Type type2) : this()
|
||||
{
|
||||
Type1 = type1;
|
||||
Type2 = type2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first type.
|
||||
/// </summary>
|
||||
public Type Type1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the second type.
|
||||
/// </summary>
|
||||
public Type Type2 { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(CompositeTypeTypeKey other)
|
||||
{
|
||||
return Type1 == other.Type1 && Type2 == other.Type2;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj is CompositeTypeTypeKey ? (CompositeTypeTypeKey)obj : default(CompositeTypeTypeKey);
|
||||
return Type1 == other.Type1 && Type2 == other.Type2;
|
||||
}
|
||||
|
||||
public static bool operator ==(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2)
|
||||
{
|
||||
return key1.Type1 == key2.Type1 && key1.Type2 == key2.Type2;
|
||||
}
|
||||
|
||||
public static bool operator !=(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2)
|
||||
{
|
||||
return key1.Type1 != key2.Type1 || key1.Type2 != key2.Type2;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (Type1.GetHashCode() * 397) ^ Type2.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a list of types.
|
||||
/// </summary>
|
||||
/// <remarks>Types in the list are, or derive from, or implement, the base type.</remarks>
|
||||
/// <typeparam name="TBase">The base type.</typeparam>
|
||||
internal class TypeList<TBase>
|
||||
{
|
||||
private readonly List<Type> _list = new List<Type>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a type to the list.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to add.</typeparam>
|
||||
public void Add<T>()
|
||||
where T : TBase
|
||||
{
|
||||
_list.Add(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a type is in the list.
|
||||
/// </summary>
|
||||
public bool Contains(Type type)
|
||||
{
|
||||
return _list.Contains(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using System.Xml.Linq;
|
||||
using ClientDependency.Core.CompositeFiles.Providers;
|
||||
using ClientDependency.Core.Config;
|
||||
using Semver;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
@@ -24,10 +28,74 @@ namespace Umbraco.Core.Configuration
|
||||
_logger = logger;
|
||||
_fileName = IOHelper.MapPath(string.Format("{0}/ClientDependency.config", SystemDirectories.Config));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the version number in ClientDependency.config to a hashed value for the version and the DateTime.Day
|
||||
/// </summary>
|
||||
/// <param name="version">The <see cref="SemVersion">version</see> of Umbraco we're upgrading to</param>
|
||||
/// <param name="date">A <see cref="DateTime">date</see> value to use in the hash to prevent this method from updating the version on each startup</param>
|
||||
/// <param name="dateFormat">Allows the developer to specify the <see cref="string">date precision</see> for the hash (i.e. "yyyyMMdd" would be a precision for the day)</param>
|
||||
/// <returns>Boolean to indicate succesful update of the ClientDependency.config file</returns>
|
||||
public bool UpdateVersionNumber(SemVersion version, DateTime date, string dateFormat)
|
||||
{
|
||||
var byteContents = Encoding.Unicode.GetBytes(version + date.ToString(dateFormat));
|
||||
|
||||
//This is a way to convert a string to a long
|
||||
//see https://www.codeproject.com/Articles/34309/Convert-String-to-bit-Integer
|
||||
//We could much more easily use MD5 which would create us an INT but since that is not compliant with
|
||||
//hashing standards we have to use SHA
|
||||
int intHash;
|
||||
using (var hash = SHA256.Create())
|
||||
{
|
||||
var bytes = hash.ComputeHash(byteContents);
|
||||
|
||||
var longResult = new[] { 0, 8, 16, 24 }
|
||||
.Select(i => BitConverter.ToInt64(bytes, i))
|
||||
.Aggregate((x, y) => x ^ y);
|
||||
|
||||
//CDF requires an INT, and although this isn't fail safe, it will work for our purposes. We are not hashing for crypto purposes
|
||||
//so there could be some collisions with this conversion but it's not a problem for our purposes
|
||||
//It's also important to note that the long.GetHashCode() implementation in .NET is this: return (int) this ^ (int) (this >> 32);
|
||||
//which means that this value will not change per appdomain like some GetHashCode implementations.
|
||||
intHash = longResult.GetHashCode();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var clientDependencyConfigXml = XDocument.Load(_fileName, LoadOptions.PreserveWhitespace);
|
||||
if (clientDependencyConfigXml.Root != null)
|
||||
{
|
||||
|
||||
var versionAttribute = clientDependencyConfigXml.Root.Attribute("version");
|
||||
|
||||
//Set the new version to the hashcode of now
|
||||
var oldVersion = versionAttribute.Value;
|
||||
var newVersion = Math.Abs(intHash).ToString();
|
||||
|
||||
//don't update if it's the same version
|
||||
if (oldVersion == newVersion)
|
||||
return false;
|
||||
|
||||
versionAttribute.SetValue(newVersion);
|
||||
clientDependencyConfigXml.Save(_fileName, SaveOptions.DisableFormatting);
|
||||
|
||||
_logger.Info<ClientDependencyConfiguration>(string.Format("Updated version number from {0} to {1}", oldVersion, newVersion));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error<ClientDependencyConfiguration>("Couldn't update ClientDependency version number", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the version number in ClientDependency.config to a random value to avoid stale caches
|
||||
/// </summary>
|
||||
/// <seealso cref="UpdateVersionNumber(SemVersion, DateTime, string)" />
|
||||
[Obsolete("Use the UpdateVersionNumber method specifying the version, date and dateFormat instead")]
|
||||
public bool IncreaseVersionNumber()
|
||||
{
|
||||
try
|
||||
@@ -107,4 +175,4 @@ namespace Umbraco.Core.Configuration
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
|
||||
{
|
||||
public class UmbracoVersion
|
||||
{
|
||||
private static readonly Version Version = new Version("7.8.0");
|
||||
private static readonly Version Version = new Version("7.9.0");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current version of Umbraco.
|
||||
|
||||
@@ -73,6 +73,11 @@
|
||||
/// </summary>
|
||||
public const string Media = "media";
|
||||
|
||||
/// <summary>
|
||||
/// alias for the macro tree.
|
||||
/// </summary>
|
||||
public const string Macros = "macros";
|
||||
|
||||
/// <summary>
|
||||
/// alias for the datatype tree.
|
||||
/// </summary>
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
|
||||
public const string AdminGroupAlias = "admin";
|
||||
public const string SensitiveDataGroupAlias = "sensitiveData";
|
||||
public const string TranslatorGroupAlias = "translator";
|
||||
|
||||
public const string BackOfficeAuthenticationType = "UmbracoBackOffice";
|
||||
@@ -36,4 +37,4 @@ namespace Umbraco.Core
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,17 +11,53 @@
|
||||
/// The integer identifier for global system root node.
|
||||
/// </summary>
|
||||
public const int Root = -1;
|
||||
|
||||
/// <summary>
|
||||
/// The string identifier for global system root node.
|
||||
/// </summary>
|
||||
/// <remarks>Use this instead of re-creating the string everywhere.</remarks>
|
||||
public const string RootString = "-1";
|
||||
|
||||
/// <summary>
|
||||
/// The integer identifier for content's recycle bin.
|
||||
/// </summary>
|
||||
public const int RecycleBinContent = -20;
|
||||
|
||||
/// <summary>
|
||||
/// The string identifier for content's recycle bin.
|
||||
/// </summary>
|
||||
/// <remarks>Use this instead of re-creating the string everywhere.</remarks>
|
||||
public const string RecycleBinContentString = "-20";
|
||||
|
||||
/// <summary>
|
||||
/// The string path prefix of the content's recycle bin.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Everything that is in the content recycle bin, has a path that starts with the prefix.</para>
|
||||
/// <para>Use this instead of re-creating the string everywhere.</para>
|
||||
/// </remarks>
|
||||
public const string RecycleBinContentPathPrefix = "-1,-20,";
|
||||
|
||||
/// <summary>
|
||||
/// The integer identifier for media's recycle bin.
|
||||
/// </summary>
|
||||
public const int RecycleBinMedia = -21;
|
||||
|
||||
/// <summary>
|
||||
/// The string identifier for media's recycle bin.
|
||||
/// </summary>
|
||||
/// <remarks>Use this instead of re-creating the string everywhere.</remarks>
|
||||
public const string RecycleBinMediaString = "-21";
|
||||
|
||||
/// <summary>
|
||||
/// The string path prefix of the media's recycle bin.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Everything that is in the media recycle bin, has a path that starts with the prefix.</para>
|
||||
/// <para>Use this instead of re-creating the string everywhere.</para>
|
||||
/// </remarks>
|
||||
public const string RecycleBinMediaPathPrefix = "-1,-21,";
|
||||
|
||||
public const int DefaultContentListViewDataTypeId = -95;
|
||||
public const int DefaultMediaListViewDataTypeId = -96;
|
||||
public const int DefaultMembersListViewDataTypeId = -97;
|
||||
@@ -29,6 +65,6 @@
|
||||
public const string UmbracoConnectionName = "umbracoDbDSN";
|
||||
public const string UmbracoMigrationName = "Umbraco";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,9 @@ namespace Umbraco.Core
|
||||
public static DateTime TruncateTo(this DateTime dt, DateTruncate truncateTo)
|
||||
{
|
||||
if (truncateTo == DateTruncate.Year)
|
||||
return new DateTime(dt.Year, 0, 0);
|
||||
return new DateTime(dt.Year, 1, 1);
|
||||
if (truncateTo == DateTruncate.Month)
|
||||
return new DateTime(dt.Year, dt.Month, 0);
|
||||
return new DateTime(dt.Year, dt.Month, 1);
|
||||
if (truncateTo == DateTruncate.Day)
|
||||
return new DateTime(dt.Year, dt.Month, dt.Day);
|
||||
if (truncateTo == DateTruncate.Hour)
|
||||
@@ -41,43 +41,43 @@ namespace Umbraco.Core
|
||||
Hour,
|
||||
Minute,
|
||||
Second
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the number of minutes from a date time, on a rolling daily basis (so if
|
||||
/// Calculates the number of minutes from a date time, on a rolling daily basis (so if
|
||||
/// date time is before the time, calculate onto next day)
|
||||
/// </summary>
|
||||
/// <param name="fromDateTime">Date to start from</param>
|
||||
/// <param name="scheduledTime">Time to compare against (in Hmm form, e.g. 330, 2200)</param>
|
||||
/// <returns></returns>
|
||||
public static int PeriodicMinutesFrom(this DateTime fromDateTime, string scheduledTime)
|
||||
{
|
||||
// Ensure time provided is 4 digits long
|
||||
if (scheduledTime.Length == 3)
|
||||
{
|
||||
scheduledTime = "0" + scheduledTime;
|
||||
}
|
||||
|
||||
var scheduledHour = int.Parse(scheduledTime.Substring(0, 2));
|
||||
var scheduledMinute = int.Parse(scheduledTime.Substring(2));
|
||||
|
||||
DateTime scheduledDateTime;
|
||||
if (IsScheduledInRemainingDay(fromDateTime, scheduledHour, scheduledMinute))
|
||||
{
|
||||
scheduledDateTime = new DateTime(fromDateTime.Year, fromDateTime.Month, fromDateTime.Day, scheduledHour, scheduledMinute, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var nextDay = fromDateTime.AddDays(1);
|
||||
scheduledDateTime = new DateTime(nextDay.Year, nextDay.Month, nextDay.Day, scheduledHour, scheduledMinute, 0);
|
||||
}
|
||||
|
||||
return (int)(scheduledDateTime - fromDateTime).TotalMinutes;
|
||||
}
|
||||
|
||||
private static bool IsScheduledInRemainingDay(DateTime fromDateTime, int scheduledHour, int scheduledMinute)
|
||||
{
|
||||
return scheduledHour > fromDateTime.Hour || (scheduledHour == fromDateTime.Hour && scheduledMinute >= fromDateTime.Minute);
|
||||
/// <returns></returns>
|
||||
public static int PeriodicMinutesFrom(this DateTime fromDateTime, string scheduledTime)
|
||||
{
|
||||
// Ensure time provided is 4 digits long
|
||||
if (scheduledTime.Length == 3)
|
||||
{
|
||||
scheduledTime = "0" + scheduledTime;
|
||||
}
|
||||
|
||||
var scheduledHour = int.Parse(scheduledTime.Substring(0, 2));
|
||||
var scheduledMinute = int.Parse(scheduledTime.Substring(2));
|
||||
|
||||
DateTime scheduledDateTime;
|
||||
if (IsScheduledInRemainingDay(fromDateTime, scheduledHour, scheduledMinute))
|
||||
{
|
||||
scheduledDateTime = new DateTime(fromDateTime.Year, fromDateTime.Month, fromDateTime.Day, scheduledHour, scheduledMinute, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var nextDay = fromDateTime.AddDays(1);
|
||||
scheduledDateTime = new DateTime(nextDay.Year, nextDay.Month, nextDay.Day, scheduledHour, scheduledMinute, 0);
|
||||
}
|
||||
|
||||
return (int)(scheduledDateTime - fromDateTime).TotalMinutes;
|
||||
}
|
||||
|
||||
private static bool IsScheduledInRemainingDay(DateTime fromDateTime, int scheduledHour, int scheduledMinute)
|
||||
{
|
||||
return scheduledHour > fromDateTime.Hour || (scheduledHour == fromDateTime.Hour && scheduledMinute >= fromDateTime.Minute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,60 +2,60 @@ using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract implementation of IDisposable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Can also be used as a pattern for when inheriting is not possible.
|
||||
///
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract implementation of IDisposable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Can also be used as a pattern for when inheriting is not possible.
|
||||
///
|
||||
/// See also: https://msdn.microsoft.com/en-us/library/b1yfkh5e%28v=vs.110%29.aspx
|
||||
/// See also: https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/
|
||||
///
|
||||
/// Note: if an object's ctor throws, it will never be disposed, and so if that ctor
|
||||
/// has allocated disposable objects, it should take care of disposing them.
|
||||
/// </remarks>
|
||||
public abstract class DisposableObject : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
private readonly object _locko = new object();
|
||||
|
||||
// gets a value indicating whether this instance is disposed.
|
||||
/// </remarks>
|
||||
public abstract class DisposableObject : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
private readonly object _locko = new object();
|
||||
|
||||
// gets a value indicating whether this instance is disposed.
|
||||
// for internal tests only (not thread safe)
|
||||
//TODO make this internal + rename "Disposed" when we can break compatibility
|
||||
public bool IsDisposed { get { return _disposed; } }
|
||||
|
||||
// implements IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
// finalizer
|
||||
~DisposableObject()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
public bool IsDisposed { get { return _disposed; } }
|
||||
|
||||
// implements IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
// finalizer
|
||||
~DisposableObject()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
//TODO make this private, non-virtual when we can break compatibility
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
lock (_locko)
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
}
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
lock (_locko)
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
DisposeUnmanagedResources();
|
||||
|
||||
if (disposing)
|
||||
DisposeResources();
|
||||
}
|
||||
|
||||
protected abstract void DisposeResources();
|
||||
|
||||
protected virtual void DisposeUnmanagedResources()
|
||||
{ }
|
||||
}
|
||||
DisposeResources();
|
||||
}
|
||||
|
||||
protected abstract void DisposeResources();
|
||||
|
||||
protected virtual void DisposeUnmanagedResources()
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// This should not be use if there are ubmanaged resources to be disposed, use DisposableObject instead
|
||||
/// </summary>
|
||||
public abstract class DisposableObjectSlim : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
private readonly object _locko = new object();
|
||||
|
||||
// gets a value indicating whether this instance is disposed.
|
||||
// for internal tests only (not thread safe)
|
||||
internal bool Disposed { get { return _disposed; } }
|
||||
|
||||
// implements IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
lock (_locko)
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
DisposeResources();
|
||||
}
|
||||
|
||||
protected abstract void DisposeResources();
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ namespace Umbraco.Core
|
||||
/// <summary>
|
||||
/// Starts the timer and invokes a callback upon disposal. Provides a simple way of timing an operation by wrapping it in a <code>using</code> (C#) statement.
|
||||
/// </summary>
|
||||
public class DisposableTimer : DisposableObject
|
||||
{
|
||||
public class DisposableTimer : DisposableObjectSlim
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly LogType? _logType;
|
||||
private readonly IProfiler _profiler;
|
||||
@@ -209,13 +209,13 @@ namespace Umbraco.Core
|
||||
loggerType,
|
||||
startMessage(),
|
||||
completeMessage());
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
|
||||
/// </summary>
|
||||
protected override void DisposeResources()
|
||||
|
||||
/// <summary>
|
||||
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObjectSlim"/> which handles common required locking logic.
|
||||
/// </summary>
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
if (_profiler != null)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Umbraco.Core.Events
|
||||
/// <summary>
|
||||
/// Event messages collection
|
||||
/// </summary>
|
||||
public sealed class EventMessages : DisposableObject
|
||||
public sealed class EventMessages : DisposableObjectSlim
|
||||
{
|
||||
private readonly List<EventMessage> _msgs = new List<EventMessage>();
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
internal class ExportedMemberEventArgs : EventArgs
|
||||
{
|
||||
public IMember Member { get; }
|
||||
public MemberExportModel Exported { get; }
|
||||
|
||||
public ExportedMemberEventArgs(IMember member, MemberExportModel exported)
|
||||
{
|
||||
Member = member;
|
||||
Exported = exported;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Umbraco.Core.Packaging.Models;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
@@ -7,18 +8,27 @@ namespace Umbraco.Core.Events
|
||||
public class ImportPackageEventArgs<TEntity> : CancellableEnumerableObjectEventArgs<TEntity>, IEquatable<ImportPackageEventArgs<TEntity>>
|
||||
{
|
||||
private readonly MetaData _packageMetaData;
|
||||
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("Use the overload specifying packageMetaData instead")]
|
||||
public ImportPackageEventArgs(TEntity eventObject, bool canCancel)
|
||||
: base(new[] { eventObject }, canCancel)
|
||||
{
|
||||
}
|
||||
|
||||
public ImportPackageEventArgs(TEntity eventObject, MetaData packageMetaData)
|
||||
: base(new[] { eventObject })
|
||||
public ImportPackageEventArgs(TEntity eventObject, MetaData packageMetaData, bool canCancel)
|
||||
: base(new[] { eventObject }, canCancel)
|
||||
{
|
||||
if (packageMetaData == null) throw new ArgumentNullException("packageMetaData");
|
||||
_packageMetaData = packageMetaData;
|
||||
}
|
||||
|
||||
public ImportPackageEventArgs(TEntity eventObject, MetaData packageMetaData)
|
||||
: this(eventObject, packageMetaData, true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public MetaData PackageMetaData
|
||||
{
|
||||
get { return _packageMetaData; }
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class RolesEventArgs : EventArgs
|
||||
{
|
||||
public RolesEventArgs(int[] memberIds, string[] roles)
|
||||
{
|
||||
MemberIds = memberIds;
|
||||
Roles = roles;
|
||||
}
|
||||
|
||||
public int[] MemberIds { get; set; }
|
||||
public string[] Roles { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -76,260 +76,257 @@ namespace Umbraco.Core.Events
|
||||
{
|
||||
if (_events == null)
|
||||
return Enumerable.Empty<IEventDefinition>();
|
||||
|
||||
|
||||
IReadOnlyList<IEventDefinition> events;
|
||||
switch (filter)
|
||||
{
|
||||
case EventDefinitionFilter.All:
|
||||
return FilterSupersededAndUpdateToLatestEntity(_events);
|
||||
events = _events;
|
||||
break;
|
||||
case EventDefinitionFilter.FirstIn:
|
||||
var l1 = new OrderedHashSet<IEventDefinition>();
|
||||
foreach (var e in _events)
|
||||
{
|
||||
l1.Add(e);
|
||||
}
|
||||
return FilterSupersededAndUpdateToLatestEntity(l1);
|
||||
events = l1;
|
||||
break;
|
||||
case EventDefinitionFilter.LastIn:
|
||||
var l2 = new OrderedHashSet<IEventDefinition>(keepOldest: false);
|
||||
foreach (var e in _events)
|
||||
{
|
||||
l2.Add(e);
|
||||
}
|
||||
return FilterSupersededAndUpdateToLatestEntity(l2);
|
||||
events = l2;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("filter", filter, null);
|
||||
}
|
||||
|
||||
return FilterSupersededAndUpdateToLatestEntity(events);
|
||||
}
|
||||
|
||||
private class EventDefinitionTypeData
|
||||
|
||||
private class EventDefinitionInfos
|
||||
{
|
||||
public IEventDefinition EventDefinition { get; set; }
|
||||
public Type EventArgType { get; set; }
|
||||
public SupersedeEventAttribute[] SupersedeAttributes { get; set; }
|
||||
public Type[] SupersedeTypes { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will iterate over the events (latest first) and filter out any events or entities in event args that are included
|
||||
/// in more recent events that Supersede previous ones. For example, If an Entity has been Saved and then Deleted, we don't want
|
||||
/// to raise the Saved event (well actually we just don't want to include it in the args for that saved event)
|
||||
/// </summary>
|
||||
/// <param name="events"></param>
|
||||
/// <returns></returns>
|
||||
private static IEnumerable<IEventDefinition> FilterSupersededAndUpdateToLatestEntity(IReadOnlyList<IEventDefinition> events)
|
||||
|
||||
// fixme
|
||||
// this is way too convoluted, the superceede attribute is used only on DeleteEventargs to specify
|
||||
// that it superceeds save, publish, move and copy - BUT - publish event args is also used for
|
||||
// unpublishing and should NOT be superceeded - so really it should not be managed at event args
|
||||
// level but at event level
|
||||
//
|
||||
// what we want is:
|
||||
// if an entity is deleted, then all Saved, Moved, Copied, Published events prior to this should
|
||||
// not trigger for the entity - and even though, does it make any sense? making a copy of an entity
|
||||
// should ... trigger?
|
||||
//
|
||||
// not going to refactor it all - we probably want to *always* trigger event but tell people that
|
||||
// due to scopes, they should not expected eg a saved entity to still be around - however, now,
|
||||
// going to write a ugly condition to deal with U4-10764
|
||||
|
||||
// iterates over the events (latest first) and filter out any events or entities in event args that are included
|
||||
// in more recent events that Supersede previous ones. For example, If an Entity has been Saved and then Deleted, we don't want
|
||||
// to raise the Saved event (well actually we just don't want to include it in the args for that saved event)
|
||||
internal static IEnumerable<IEventDefinition> FilterSupersededAndUpdateToLatestEntity(IReadOnlyList<IEventDefinition> events)
|
||||
{
|
||||
//used to keep the 'latest' entity and associated event definition data
|
||||
var allEntities = new List<Tuple<IEntity, EventDefinitionTypeData>>();
|
||||
|
||||
//tracks all CancellableObjectEventArgs instances in the events which is the only type of args we can work with
|
||||
var cancelableArgs = new List<CancellableObjectEventArgs>();
|
||||
// keeps the 'latest' entity and associated event data
|
||||
var entities = new List<Tuple<IEntity, EventDefinitionInfos>>();
|
||||
|
||||
// collects the event definitions
|
||||
// collects the arguments in result, that require their entities to be updated
|
||||
var result = new List<IEventDefinition>();
|
||||
var resultArgs = new List<CancellableObjectEventArgs>();
|
||||
|
||||
//This will eagerly load all of the event arg types and their attributes so we don't have to continuously look this data up
|
||||
var allArgTypesWithAttributes = events.Select(x => x.Args.GetType())
|
||||
// eagerly fetch superceeded arg types for each arg type
|
||||
var argTypeSuperceeding = events.Select(x => x.Args.GetType())
|
||||
.Distinct()
|
||||
.ToDictionary(x => x, x => x.GetCustomAttributes<SupersedeEventAttribute>(false).ToArray());
|
||||
|
||||
//Iterate all events and collect the actual entities in them and relates them to their corresponding EventDefinitionTypeData
|
||||
//we'll process the list in reverse because events are added in the order they are raised and we want to filter out
|
||||
//any entities from event args that are not longer relevant
|
||||
//(i.e. if an item is Deleted after it's Saved, we won't include the item in the Saved args)
|
||||
.ToDictionary(x => x, x => x.GetCustomAttributes<SupersedeEventAttribute>(false).Select(y => y.SupersededEventArgsType).ToArray());
|
||||
|
||||
// iterate over all events and filter
|
||||
//
|
||||
// process the list in reverse, because events are added in the order they are raised and we want to keep
|
||||
// the latest (most recent) entities and filter out what is not relevant anymore (too old), eg if an entity
|
||||
// is Deleted after being Saved, we want to filter out the Saved event
|
||||
for (var index = events.Count - 1; index >= 0; index--)
|
||||
{
|
||||
var eventDefinition = events[index];
|
||||
var def = events[index];
|
||||
|
||||
var argType = eventDefinition.Args.GetType();
|
||||
var attributes = allArgTypesWithAttributes[eventDefinition.Args.GetType()];
|
||||
|
||||
var meta = new EventDefinitionTypeData
|
||||
var infos = new EventDefinitionInfos
|
||||
{
|
||||
EventDefinition = eventDefinition,
|
||||
EventArgType = argType,
|
||||
SupersedeAttributes = attributes
|
||||
EventDefinition = def,
|
||||
SupersedeTypes = argTypeSuperceeding[def.Args.GetType()]
|
||||
};
|
||||
|
||||
var args = eventDefinition.Args as CancellableObjectEventArgs;
|
||||
if (args != null)
|
||||
var args = def.Args as CancellableObjectEventArgs;
|
||||
if (args == null)
|
||||
{
|
||||
var list = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject);
|
||||
|
||||
if (list == null)
|
||||
// not a cancellable event arg, include event definition in result
|
||||
result.Add(def);
|
||||
}
|
||||
else
|
||||
{
|
||||
// event object can either be a single object or an enumerable of objects
|
||||
// try to get as an enumerable, get null if it's not
|
||||
var eventObjects = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject);
|
||||
if (eventObjects == null)
|
||||
{
|
||||
//extract the event object
|
||||
var obj = args.EventObject as IEntity;
|
||||
if (obj != null)
|
||||
// single object, cast as an IEntity
|
||||
// if cannot cast, cannot filter, nothing - just include event definition in result
|
||||
var eventEntity = args.EventObject as IEntity;
|
||||
if (eventEntity == null)
|
||||
{
|
||||
//Now check if this entity already exists in other event args that supersede this current event arg type
|
||||
if (IsFiltered(obj, meta, allEntities) == false)
|
||||
{
|
||||
//if it's not filtered we can adde these args to the response
|
||||
cancelableArgs.Add(args);
|
||||
result.Add(eventDefinition);
|
||||
//track the entity
|
||||
allEntities.Add(Tuple.Create(obj, meta));
|
||||
}
|
||||
result.Add(def);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
// look for this entity in superceding event args
|
||||
// found = must be removed (ie not added), else track
|
||||
if (IsSuperceeded(eventEntity, infos, entities) == false)
|
||||
{
|
||||
//Can't retrieve the entity so cant' filter or inspect, just add to the output
|
||||
result.Add(eventDefinition);
|
||||
// track
|
||||
entities.Add(Tuple.Create(eventEntity, infos));
|
||||
|
||||
// track result arguments
|
||||
// include event definition in result
|
||||
resultArgs.Add(args);
|
||||
result.Add(def);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// enumerable of objects
|
||||
var toRemove = new List<IEntity>();
|
||||
foreach (var entity in list)
|
||||
foreach (var eventObject in eventObjects)
|
||||
{
|
||||
//extract the event object
|
||||
var obj = entity as IEntity;
|
||||
if (obj != null)
|
||||
{
|
||||
//Now check if this entity already exists in other event args that supersede this current event arg type
|
||||
if (IsFiltered(obj, meta, allEntities))
|
||||
{
|
||||
//track it to be removed
|
||||
toRemove.Add(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
//track the entity, it's not filtered
|
||||
allEntities.Add(Tuple.Create(obj, meta));
|
||||
}
|
||||
}
|
||||
// extract the event object, cast as an IEntity
|
||||
// if cannot cast, cannot filter, nothing to do - just leave it in the list & continue
|
||||
var eventEntity = eventObject as IEntity;
|
||||
if (eventEntity == null)
|
||||
continue;
|
||||
|
||||
// look for this entity in superceding event args
|
||||
// found = must be removed, else track
|
||||
if (IsSuperceeded(eventEntity, infos, entities))
|
||||
toRemove.Add(eventEntity);
|
||||
else
|
||||
{
|
||||
//we don't need to do anything here, we can't cast to IEntity so we cannot filter, so it will just remain in the list
|
||||
}
|
||||
entities.Add(Tuple.Create(eventEntity, infos));
|
||||
}
|
||||
|
||||
//remove anything that has been filtered
|
||||
// remove superceded entities
|
||||
foreach (var entity in toRemove)
|
||||
{
|
||||
list.Remove(entity);
|
||||
}
|
||||
eventObjects.Remove(entity);
|
||||
|
||||
//track the event and include in the response if there's still entities remaining in the list
|
||||
if (list.Count > 0)
|
||||
// if there are still entities in the list, keep the event definition
|
||||
if (eventObjects.Count > 0)
|
||||
{
|
||||
if (toRemove.Count > 0)
|
||||
{
|
||||
//re-assign if the items have changed
|
||||
args.EventObject = list;
|
||||
// re-assign if changed
|
||||
args.EventObject = eventObjects;
|
||||
}
|
||||
cancelableArgs.Add(args);
|
||||
result.Add(eventDefinition);
|
||||
|
||||
// track result arguments
|
||||
// include event definition in result
|
||||
resultArgs.Add(args);
|
||||
result.Add(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//it's not a cancelable event arg so we just include it in the result
|
||||
result.Add(eventDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
//Now we'll deal with ensuring that only the latest(non stale) entities are used throughout all event args
|
||||
UpdateToLatestEntities(allEntities, cancelableArgs);
|
||||
// go over all args in result, and update them with the latest instanceof each entity
|
||||
UpdateToLatestEntities(entities, resultArgs);
|
||||
|
||||
//we need to reverse the result since we've been adding by latest added events first!
|
||||
// reverse, since we processed the list in reverse
|
||||
result.Reverse();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void UpdateToLatestEntities(IEnumerable<Tuple<IEntity, EventDefinitionTypeData>> allEntities, IEnumerable<CancellableObjectEventArgs> cancelableArgs)
|
||||
// edits event args to use the latest instance of each entity
|
||||
private static void UpdateToLatestEntities(IEnumerable<Tuple<IEntity, EventDefinitionInfos>> entities, IEnumerable<CancellableObjectEventArgs> args)
|
||||
{
|
||||
//Now we'll deal with ensuring that only the latest(non stale) entities are used throughout all event args
|
||||
|
||||
// get the latest entities
|
||||
// ordered hash set + keepOldest will keep the latest inserted entity (in case of duplicates)
|
||||
var latestEntities = new OrderedHashSet<IEntity>(keepOldest: true);
|
||||
foreach (var entity in allEntities.OrderByDescending(entity => entity.Item1.UpdateDate))
|
||||
{
|
||||
foreach (var entity in entities.OrderByDescending(entity => entity.Item1.UpdateDate))
|
||||
latestEntities.Add(entity.Item1);
|
||||
}
|
||||
|
||||
foreach (var args in cancelableArgs)
|
||||
foreach (var arg in args)
|
||||
{
|
||||
var list = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject);
|
||||
if (list == null)
|
||||
// event object can either be a single object or an enumerable of objects
|
||||
// try to get as an enumerable, get null if it's not
|
||||
var eventObjects = TypeHelper.CreateGenericEnumerableFromObject(arg.EventObject);
|
||||
if (eventObjects == null)
|
||||
{
|
||||
//try to find the args entity in the latest entity - based on the equality operators, this will
|
||||
//match by Id since that is the default equality checker for IEntity. If one is found, than it is
|
||||
//the most recent entity instance so update the args with that instance so we don't emit a stale instance.
|
||||
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, args.EventObject));
|
||||
// single object
|
||||
// look for a more recent entity for that object, and replace if any
|
||||
// works by "equalling" entities ie the more recent one "equals" this one (though different object)
|
||||
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, arg.EventObject));
|
||||
if (foundEntity != null)
|
||||
{
|
||||
args.EventObject = foundEntity;
|
||||
}
|
||||
arg.EventObject = foundEntity;
|
||||
}
|
||||
else
|
||||
{
|
||||
// enumerable of objects
|
||||
// same as above but for each object
|
||||
var updated = false;
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
for (var i = 0; i < eventObjects.Count; i++)
|
||||
{
|
||||
//try to find the args entity in the latest entity - based on the equality operators, this will
|
||||
//match by Id since that is the default equality checker for IEntity. If one is found, than it is
|
||||
//the most recent entity instance so update the args with that instance so we don't emit a stale instance.
|
||||
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, list[i]));
|
||||
if (foundEntity != null)
|
||||
{
|
||||
list[i] = foundEntity;
|
||||
updated = true;
|
||||
}
|
||||
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, eventObjects[i]));
|
||||
if (foundEntity == null) continue;
|
||||
eventObjects[i] = foundEntity;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (updated)
|
||||
{
|
||||
args.EventObject = list;
|
||||
}
|
||||
arg.EventObject = eventObjects;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will check against all of the processed entity/events (allEntities) to see if this entity already exists in
|
||||
/// event args that supersede the event args being passed in and if so returns true.
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="eventDef"></param>
|
||||
/// <param name="allEntities"></param>
|
||||
/// <returns></returns>
|
||||
private static bool IsFiltered(
|
||||
IEntity entity,
|
||||
EventDefinitionTypeData eventDef,
|
||||
List<Tuple<IEntity, EventDefinitionTypeData>> allEntities)
|
||||
// determines if a given entity, appearing in a given event definition, should be filtered out,
|
||||
// considering the entities that have already been visited - an entity is filtered out if it
|
||||
// appears in another even definition, which superceedes this event definition.
|
||||
private static bool IsSuperceeded(IEntity entity, EventDefinitionInfos infos, List<Tuple<IEntity, EventDefinitionInfos>> entities)
|
||||
{
|
||||
var argType = eventDef.EventDefinition.Args.GetType();
|
||||
//var argType = meta.EventArgsType;
|
||||
var argType = infos.EventDefinition.Args.GetType();
|
||||
|
||||
//check if the entity is found in any processed event data that could possible supersede this one
|
||||
var foundByEntity = allEntities
|
||||
.Where(x => x.Item2.SupersedeAttributes.Length > 0
|
||||
//if it's the same arg type than it cannot supersede
|
||||
&& x.Item2.EventArgType != argType
|
||||
&& Equals(x.Item1, entity))
|
||||
// look for other instances of the same entity, coming from an event args that supercedes other event args,
|
||||
// ie is marked with the attribute, and is not this event args (cannot supersede itself)
|
||||
var superceeding = entities
|
||||
.Where(x => x.Item2.SupersedeTypes.Length > 0 // has the attribute
|
||||
&& x.Item2.EventDefinition.Args.GetType() != argType // is not the same
|
||||
&& Equals(x.Item1, entity)) // same entity
|
||||
.ToArray();
|
||||
|
||||
//no args have been processed with this entity so it should not be filtered
|
||||
if (foundByEntity.Length == 0)
|
||||
// first time we see this entity = not filtered
|
||||
if (superceeding.Length == 0)
|
||||
return false;
|
||||
|
||||
// fixme see notes above
|
||||
// delete event args does NOT superceedes 'unpublished' event
|
||||
if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "UnPublished")
|
||||
return false;
|
||||
|
||||
// found occurences, need to determine if this event args is superceded
|
||||
if (argType.IsGenericType)
|
||||
{
|
||||
var supercededBy = foundByEntity
|
||||
.FirstOrDefault(x =>
|
||||
x.Item2.SupersedeAttributes.Any(y =>
|
||||
//if the attribute type is a generic type def then compare with the generic type def of the event arg
|
||||
(y.SupersededEventArgsType.IsGenericTypeDefinition && y.SupersededEventArgsType == argType.GetGenericTypeDefinition())
|
||||
//if the attribute type is not a generic type def then compare with the normal type of the event arg
|
||||
|| (y.SupersededEventArgsType.IsGenericTypeDefinition == false && y.SupersededEventArgsType == argType)));
|
||||
// generic, must compare type arguments
|
||||
var supercededBy = superceeding.FirstOrDefault(x =>
|
||||
x.Item2.SupersedeTypes.Any(y =>
|
||||
// superceeding a generic type which has the same generic type definition
|
||||
// fixme no matter the generic type parameters? could be different?
|
||||
y.IsGenericTypeDefinition && y == argType.GetGenericTypeDefinition()
|
||||
// or superceeding a non-generic type which is ... fixme how is this ever possible? argType *is* generic?
|
||||
|| y.IsGenericTypeDefinition == false && y == argType));
|
||||
return supercededBy != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var supercededBy = foundByEntity
|
||||
.FirstOrDefault(x =>
|
||||
x.Item2.SupersedeAttributes.Any(y =>
|
||||
//since the event arg type is not a generic type, then we just compare type 1:1
|
||||
y.SupersededEventArgsType == argType));
|
||||
// non-generic, can compare types 1:1
|
||||
var supercededBy = superceeding.FirstOrDefault(x =>
|
||||
x.Item2.SupersedeTypes.Any(y => y == argType));
|
||||
return supercededBy != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
internal class UserGroupWithUsers
|
||||
{
|
||||
public UserGroupWithUsers(IUserGroup userGroup, IUser[] addedUsers, IUser[] removedUsers)
|
||||
{
|
||||
UserGroup = userGroup;
|
||||
AddedUsers = addedUsers;
|
||||
RemovedUsers = removedUsers;
|
||||
}
|
||||
|
||||
public IUserGroup UserGroup { get; }
|
||||
public IUser[] AddedUsers { get; }
|
||||
public IUser[] RemovedUsers { get; }
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace Umbraco.Core
|
||||
/// This will use the crypto libs to generate the hash and will try to ensure that
|
||||
/// strings, etc... are not re-allocated so it's not consuming much memory.
|
||||
/// </remarks>
|
||||
internal class HashGenerator : DisposableObject
|
||||
internal class HashGenerator : DisposableObjectSlim
|
||||
{
|
||||
public HashGenerator()
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
@@ -107,7 +108,7 @@ namespace Umbraco.Core.IO
|
||||
|
||||
try
|
||||
{
|
||||
Directory.Delete(fullPath, recursive);
|
||||
WithRetry(() => Directory.Delete(fullPath, recursive));
|
||||
}
|
||||
catch (DirectoryNotFoundException ex)
|
||||
{
|
||||
@@ -225,8 +226,8 @@ namespace Umbraco.Core.IO
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(fullPath);
|
||||
{
|
||||
WithRetry(() => File.Delete(fullPath));
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
@@ -378,7 +379,7 @@ namespace Umbraco.Core.IO
|
||||
{
|
||||
if (overrideIfExists == false)
|
||||
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
|
||||
File.Delete(fullPath);
|
||||
WithRetry(() => File.Delete(fullPath));
|
||||
}
|
||||
|
||||
var directory = Path.GetDirectoryName(fullPath);
|
||||
@@ -386,9 +387,9 @@ namespace Umbraco.Core.IO
|
||||
Directory.CreateDirectory(directory); // ensure it exists
|
||||
|
||||
if (copy)
|
||||
File.Copy(physicalPath, fullPath);
|
||||
WithRetry(() => File.Copy(physicalPath, fullPath));
|
||||
else
|
||||
File.Move(physicalPath, fullPath);
|
||||
WithRetry(() => File.Move(physicalPath, fullPath));
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
@@ -417,6 +418,35 @@ namespace Umbraco.Core.IO
|
||||
return path;
|
||||
}
|
||||
|
||||
protected void WithRetry(Action action)
|
||||
{
|
||||
// 10 times 100ms is 1s
|
||||
const int count = 10;
|
||||
const int pausems = 100;
|
||||
|
||||
for (var i = 0;; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
break; // done
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// if it's not *exactly* IOException then it could be
|
||||
// some inherited exception such as FileNotFoundException,
|
||||
// and then we don't want to retry
|
||||
if (e.GetType() != typeof(IOException)) throw;
|
||||
|
||||
// if we have tried enough, throw, else swallow
|
||||
// the exception and retry after a pause
|
||||
if (i == count) throw;
|
||||
}
|
||||
|
||||
Thread.Sleep(pausems);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using log4net.Appender;
|
||||
using log4net.Util;
|
||||
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// This class will do the exact same thing as the RollingFileAppender that comes from log4net
|
||||
/// With the extension, that it is able to do automatic cleanup of the logfiles in the directory where logging happens
|
||||
///
|
||||
/// By specifying the properties MaxLogFileDays and BaseFilePattern, the files will automaticly get deleted when
|
||||
/// the logger is configured(typically when the app starts). To utilize this appender swap out the type of the rollingFile appender
|
||||
/// that ships with Umbraco, to be Umbraco.Core.Logging.RollingFileCleanupAppender, and add the maxLogFileDays and baseFilePattern elements
|
||||
/// to the configuration i.e.:
|
||||
///
|
||||
/// <example>
|
||||
/// <appender name="rollingFile" type="Log4netAwesomeness.CustomRollingFileAppender, Log4netAwesomeness">
|
||||
/// <file type="log4net.Util.PatternString" value="App_Data\Logs\UmbracoTraceLog.%property{log4net:HostName}.txt" />
|
||||
/// <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
|
||||
/// <appendToFile value="true" />
|
||||
/// <rollingStyle value="Date" />
|
||||
/// <maximumFileSize value="5MB" />
|
||||
/// <maxLogFileDays value="5"/>
|
||||
/// <basefilePattern value="UmbracoTraceLog.*.txt.*"/>
|
||||
/// <layout type="log4net.Layout.PatternLayout">
|
||||
/// <conversionPattern value=" %date [P%property{processId}/D%property{appDomainId}/T%thread] %-5level %logger - %message%newline" />
|
||||
/// </layout>
|
||||
/// <layout type="log4net.Layout.PatternLayout">
|
||||
/// <conversionPattern value=" %date [P%property{processId}/D%property{appDomainId}/T%thread] %-5level %logger - %message%newline" />
|
||||
/// </layout>
|
||||
/// <encoding value="utf-8" />
|
||||
/// </appender>
|
||||
/// </example>
|
||||
/// </summary>
|
||||
public class RollingFileCleanupAppender : RollingFileAppender
|
||||
{
|
||||
public int MaxLogFileDays { get; set; }
|
||||
public string BaseFilePattern { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This override will delete logs older than the specified amount of days
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="append"></param>
|
||||
protected override void OpenFile(string fileName, bool append)
|
||||
{
|
||||
bool cleanup = true;
|
||||
// Validate settings and input
|
||||
if (MaxLogFileDays <= 0)
|
||||
{
|
||||
LogLog.Warn(typeof(RollingFileCleanupAppender), "Parameter 'MaxLogFileDays' needs to be a positive integer, aborting cleanup");
|
||||
cleanup = false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(BaseFilePattern))
|
||||
{
|
||||
LogLog.Warn(typeof(RollingFileCleanupAppender), "Parameter 'BaseFilePattern' is empty, aborting cleanup");
|
||||
cleanup = false;
|
||||
}
|
||||
// grab the directory we are logging to, as this is were we will search for older logfiles
|
||||
var logFolder = Path.GetDirectoryName(fileName);
|
||||
if (Directory.Exists(logFolder) == false)
|
||||
{
|
||||
LogLog.Warn(typeof(RollingFileCleanupAppender), string.Format("Directory '{0}' for logfiles does not exist, aborting cleanup", logFolder));
|
||||
cleanup = false;
|
||||
}
|
||||
// If everything is validated, we can do the actual cleanup
|
||||
if (cleanup)
|
||||
{
|
||||
Cleanup(logFolder);
|
||||
}
|
||||
|
||||
base.OpenFile(fileName, append);
|
||||
}
|
||||
|
||||
private void Cleanup(string directoryPath)
|
||||
{
|
||||
// only take files that matches the pattern we are using i.e. UmbracoTraceLog.*.txt.*
|
||||
string[] logFiles = Directory.GetFiles(directoryPath, BaseFilePattern);
|
||||
LogLog.Debug(typeof(RollingFileCleanupAppender), string.Format("Found {0} files that matches the baseFilePattern: '{1}'", logFiles.Length, BaseFilePattern));
|
||||
|
||||
foreach (var logFile in logFiles)
|
||||
{
|
||||
DateTime lastAccessTime = System.IO.File.GetLastWriteTimeUtc(logFile);
|
||||
// take the value from the config file
|
||||
if (lastAccessTime < DateTime.Now.AddDays(-MaxLogFileDays))
|
||||
{
|
||||
LogLog.Debug(typeof(RollingFileCleanupAppender), string.Format("Deleting file {0} as its lastAccessTime is older than {1} days speficied by MaxLogFileDays", logFile, MaxLogFileDays));
|
||||
base.DeleteFile(logFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.Manifest
|
||||
{
|
||||
internal class ManifestWatcher : DisposableObject
|
||||
internal class ManifestWatcher : DisposableObjectSlim
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly List<FileSystemWatcher> _fws = new List<FileSystemWatcher>();
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an audited event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
internal class AuditEntry : Entity, IAuditEntry
|
||||
{
|
||||
private static PropertySelectors _selectors;
|
||||
|
||||
private int _performingUserId;
|
||||
private string _performingDetails;
|
||||
private string _performingIp;
|
||||
private int _affectedUserId;
|
||||
private string _affectedDetails;
|
||||
private string _eventType;
|
||||
private string _eventDetails;
|
||||
|
||||
private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors());
|
||||
|
||||
private class PropertySelectors
|
||||
{
|
||||
public readonly PropertyInfo PerformingUserId = ExpressionHelper.GetPropertyInfo<AuditEntry, int>(x => x.PerformingUserId);
|
||||
public readonly PropertyInfo PerformingDetails = ExpressionHelper.GetPropertyInfo<AuditEntry, string>(x => x.PerformingDetails);
|
||||
public readonly PropertyInfo PerformingIp = ExpressionHelper.GetPropertyInfo<AuditEntry, string>(x => x.PerformingIp);
|
||||
public readonly PropertyInfo AffectedUserId = ExpressionHelper.GetPropertyInfo<AuditEntry, int>(x => x.AffectedUserId);
|
||||
public readonly PropertyInfo AffectedDetails = ExpressionHelper.GetPropertyInfo<AuditEntry, string>(x => x.AffectedDetails);
|
||||
public readonly PropertyInfo EventType = ExpressionHelper.GetPropertyInfo<AuditEntry, string>(x => x.EventType);
|
||||
public readonly PropertyInfo EventDetails = ExpressionHelper.GetPropertyInfo<AuditEntry, string>(x => x.EventDetails);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int PerformingUserId
|
||||
{
|
||||
get => _performingUserId;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _performingUserId, Selectors.PerformingUserId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string PerformingDetails
|
||||
{
|
||||
get => _performingDetails;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _performingDetails, Selectors.PerformingDetails);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string PerformingIp
|
||||
{
|
||||
get => _performingIp;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _performingIp, Selectors.PerformingIp);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime EventDateUtc
|
||||
{
|
||||
get => CreateDate;
|
||||
set => CreateDate = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int AffectedUserId
|
||||
{
|
||||
get => _affectedUserId;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _affectedUserId, Selectors.AffectedUserId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string AffectedDetails
|
||||
{
|
||||
get => _affectedDetails;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _affectedDetails, Selectors.AffectedDetails);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string EventType
|
||||
{
|
||||
get => _eventType;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _eventType, Selectors.EventType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string EventDetails
|
||||
{
|
||||
get => _eventDetails;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _eventDetails, Selectors.EventDetails);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a consent.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
internal class Consent : Entity, IConsent
|
||||
{
|
||||
private static PropertySelectors _selector;
|
||||
|
||||
private bool _current;
|
||||
private string _source;
|
||||
private string _context;
|
||||
private string _action;
|
||||
private ConsentState _state;
|
||||
private string _comment;
|
||||
|
||||
// ReSharper disable once ClassNeverInstantiated.Local
|
||||
private class PropertySelectors
|
||||
{
|
||||
public readonly PropertyInfo Current = ExpressionHelper.GetPropertyInfo<Consent, bool>(x => x.Current);
|
||||
public readonly PropertyInfo Source = ExpressionHelper.GetPropertyInfo<Consent, string>(x => x.Source);
|
||||
public readonly PropertyInfo Context = ExpressionHelper.GetPropertyInfo<Consent, string>(x => x.Context);
|
||||
public readonly PropertyInfo Action = ExpressionHelper.GetPropertyInfo<Consent, string>(x => x.Action);
|
||||
public readonly PropertyInfo State = ExpressionHelper.GetPropertyInfo<Consent, ConsentState>(x => x.State);
|
||||
public readonly PropertyInfo Comment = ExpressionHelper.GetPropertyInfo<Consent, string>(x => x.Comment);
|
||||
}
|
||||
|
||||
private static PropertySelectors Selectors => _selector ?? (_selector = new PropertySelectors());
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Current
|
||||
{
|
||||
get => _current;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _current, Selectors.Current);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Source
|
||||
{
|
||||
get => _source;
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value));
|
||||
SetPropertyValueAndDetectChanges(value, ref _source, Selectors.Source);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Context
|
||||
{
|
||||
get => _context;
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value));
|
||||
SetPropertyValueAndDetectChanges(value, ref _context, Selectors.Context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Action
|
||||
{
|
||||
get => _action;
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value));
|
||||
SetPropertyValueAndDetectChanges(value, ref _action, Selectors.Action);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ConsentState State
|
||||
{
|
||||
get => _state;
|
||||
// note: we probably should validate the state here, but since the
|
||||
// enum is [Flags] with many combinations, this could be expensive
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _state, Selectors.State);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Comment
|
||||
{
|
||||
get => _comment;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _comment, Selectors.Comment);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IConsent> History => HistoryInternal;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the previous states of this consent.
|
||||
/// </summary>
|
||||
internal List<IConsent> HistoryInternal { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for the <see cref="IConsent"/> interface.
|
||||
/// </summary>
|
||||
public static class ConsentExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the consent is granted.
|
||||
/// </summary>
|
||||
public static bool IsGranted(this IConsent consent) => (consent.State & ConsentState.Granted) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the consent is revoked.
|
||||
/// </summary>
|
||||
public static bool IsRevoked(this IConsent consent) => (consent.State & ConsentState.Revoked) > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the state of a consent.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ConsentState // : int
|
||||
{
|
||||
// note - this is a [Flags] enumeration
|
||||
// on can create detailed flags such as:
|
||||
//GrantedOptIn = Granted | 0x0001
|
||||
//GrandedByForce = Granted | 0x0002
|
||||
//
|
||||
// 16 situations for each Pending/Granted/Revoked should be ok
|
||||
|
||||
/// <summary>
|
||||
/// There is no consent.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Consent is pending and has not been granted yet.
|
||||
/// </summary>
|
||||
Pending = 0x10000,
|
||||
|
||||
/// <summary>
|
||||
/// Consent has been granted.
|
||||
/// </summary>
|
||||
Granted = 0x20000,
|
||||
|
||||
/// <summary>
|
||||
/// Consent has been revoked.
|
||||
/// </summary>
|
||||
Revoked = 0x40000
|
||||
}
|
||||
}
|
||||
@@ -544,6 +544,28 @@ namespace Umbraco.Core.Models
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns both instance dirty properties and property type properties
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override IEnumerable<string> GetDirtyProperties()
|
||||
{
|
||||
var instanceProperties = base.GetDirtyProperties();
|
||||
var propertyTypes = Properties.Where(x => x.IsDirty()).Select(x => x.Alias);
|
||||
return instanceProperties.Concat(propertyTypes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns both instance dirty properties and property type properties
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal override IEnumerable<string> GetPreviouslyDirtyProperties()
|
||||
{
|
||||
var instanceProperties = base.GetPreviouslyDirtyProperties();
|
||||
var propertyTypes = Properties.Where(x => x.WasDirty()).Select(x => x.Alias);
|
||||
return instanceProperties.Concat(propertyTypes);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,8 +95,10 @@ namespace Umbraco.Core.Models
|
||||
get { return _allowedTemplates; }
|
||||
set
|
||||
{
|
||||
SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector,
|
||||
Ps.Value.TemplateComparer);
|
||||
SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector, Ps.Value.TemplateComparer);
|
||||
|
||||
if (_allowedTemplates.Any(x => x.Id == _defaultTemplate) == false)
|
||||
DefaultTemplateId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,13 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
: _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key);
|
||||
}
|
||||
|
||||
internal virtual IEnumerable<string> GetPreviouslyDirtyProperties()
|
||||
{
|
||||
return _lastPropertyChangedInfo == null
|
||||
? Enumerable.Empty<string>()
|
||||
: _lastPropertyChangedInfo.Where(x => x.Value).Select(x => x.Key);
|
||||
}
|
||||
|
||||
private bool _changeTrackingEnabled = true;
|
||||
|
||||
/// <summary>
|
||||
@@ -221,4 +228,4 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an audited event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The free-form details properties can be used to capture relevant infos (for example,
|
||||
/// a user email and identifier) at the time of the audited event, even though they may change
|
||||
/// later on - but we want to keep a track of their value at that time.</para>
|
||||
/// <para>Depending on audit loggers, these properties can be purely free-form text, or
|
||||
/// contain json serialized objects.</para>
|
||||
/// </remarks>
|
||||
public interface IAuditEntry : IAggregateRoot, IRememberBeingDirty
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the user triggering the audited event.
|
||||
/// </summary>
|
||||
int PerformingUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets free-form details about the user triggering the audited event.
|
||||
/// </summary>
|
||||
string PerformingDetails { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the IP address or the request triggering the audited event.
|
||||
/// </summary>
|
||||
string PerformingIp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date and time of the audited event.
|
||||
/// </summary>
|
||||
DateTime EventDateUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the user affected by the audited event.
|
||||
/// </summary>
|
||||
/// <remarks>Not used when no single user is affected by the event.</remarks>
|
||||
int AffectedUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets free-form details about the entity affected by the audited event.
|
||||
/// </summary>
|
||||
/// <remarks>The entity affected by the event can be another user, a member...</remarks>
|
||||
string AffectedDetails { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the audited event.
|
||||
/// </summary>
|
||||
string EventType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets free-form details about the audited event.
|
||||
/// </summary>
|
||||
string EventDetails { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a consent state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>A consent is fully identified by a source (whoever is consenting), a context (for
|
||||
/// example, an application), and an action (whatever is consented).</para>
|
||||
/// <para>A consent state registers the state of the consent (granted, revoked...).</para>
|
||||
/// </remarks>
|
||||
public interface IConsent : IAggregateRoot, IRememberBeingDirty
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the consent entity represents the current state.
|
||||
/// </summary>
|
||||
bool Current { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of whoever is consenting.
|
||||
/// </summary>
|
||||
string Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the context of the consent.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Represents the domain, application, scope... of the action.</para>
|
||||
/// <para>When the action is a Udi, this should be the Udi type.</para>
|
||||
/// </remarks>
|
||||
string Context { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the consented action.
|
||||
/// </summary>
|
||||
string Action { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the consent.
|
||||
/// </summary>
|
||||
ConsentState State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets some additional free text.
|
||||
/// </summary>
|
||||
string Comment { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the previous states of this consent.
|
||||
/// </summary>
|
||||
IEnumerable<IConsent> History { get; }
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,13 @@
|
||||
/// <returns></returns>
|
||||
bool MemberCanViewProperty(string propertyTypeAlias);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile.
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to check</param>
|
||||
/// <returns></returns>
|
||||
bool IsSensitiveProperty(string propertyTypeAlias);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a boolean indicating whether a Property is editable by the Member.
|
||||
/// </summary>
|
||||
@@ -32,5 +39,12 @@
|
||||
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
|
||||
/// <param name="value">Boolean value, true or false</param>
|
||||
void SetMemberCanViewProperty(string propertyTypeAlias, bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a boolean indicating whether a Property is a sensitive value on the Members profile.
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
|
||||
/// <param name="value">Boolean value, true or false</param>
|
||||
void SetIsSensitiveProperty(string propertyTypeAlias, bool value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,15 @@ namespace Umbraco.Core.Models.Identity
|
||||
.ForMember(user => user.CalculatedContentStartNodeIds, expression => expression.MapFrom(user => user.CalculateContentStartNodeIds(applicationContext.Services.EntityService)))
|
||||
.ForMember(user => user.CalculatedMediaStartNodeIds, expression => expression.MapFrom(user => user.CalculateMediaStartNodeIds(applicationContext.Services.EntityService)))
|
||||
.ForMember(user => user.AllowedSections, expression => expression.MapFrom(user => user.AllowedSections.ToArray()))
|
||||
|
||||
.ForMember(user => user.LockoutEnabled, expression => expression.Ignore())
|
||||
.ForMember(user => user.Logins, expression => expression.Ignore())
|
||||
.ForMember(user => user.Roles, expression => expression.Ignore())
|
||||
.ForMember(user => user.PhoneNumber, expression => expression.Ignore())
|
||||
.ForMember(user => user.PhoneNumberConfirmed, expression => expression.Ignore())
|
||||
.ForMember(user => user.TwoFactorEnabled, expression => expression.Ignore())
|
||||
.ForMember(user => user.Claims, expression => expression.Ignore())
|
||||
|
||||
.AfterMap((user, identityUser) =>
|
||||
{
|
||||
identityUser.ResetDirtyProperties(true);
|
||||
@@ -47,4 +56,4 @@ namespace Umbraco.Core.Models.Identity
|
||||
return storedPass.StartsWith(Constants.Security.EmptyPasswordPrefix) ? null : storedPass;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,19 @@ namespace Umbraco.Core.Models
|
||||
public string RawPasswordValue
|
||||
{
|
||||
get { return _rawPasswordValue; }
|
||||
set { SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
//special case, this is used to ensure that the password is not updated when persisting, in this case
|
||||
//we don't want to track changes either
|
||||
_rawPasswordValue = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -628,4 +640,4 @@ namespace Umbraco.Core.Models
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile) by the PropertyTypes' alias.
|
||||
/// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile, IsSensitive) by the PropertyTypes' alias.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
internal IDictionary<string, MemberTypePropertyProfileAccess> MemberTypePropertyTypes { get; private set; }
|
||||
@@ -76,11 +76,11 @@ namespace Umbraco.Core.Models
|
||||
/// <returns></returns>
|
||||
public bool MemberCanEditProperty(string propertyTypeAlias)
|
||||
{
|
||||
if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias))
|
||||
MemberTypePropertyProfileAccess propertyProfile;
|
||||
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
|
||||
{
|
||||
return MemberTypePropertyTypes[propertyTypeAlias].IsEditable;
|
||||
return propertyProfile.IsEditable;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -91,11 +91,26 @@ namespace Umbraco.Core.Models
|
||||
/// <returns></returns>
|
||||
public bool MemberCanViewProperty(string propertyTypeAlias)
|
||||
{
|
||||
if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias))
|
||||
MemberTypePropertyProfileAccess propertyProfile;
|
||||
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
|
||||
{
|
||||
return MemberTypePropertyTypes[propertyTypeAlias].IsVisible;
|
||||
return propertyProfile.IsVisible;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile.
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to check</param>
|
||||
/// <returns></returns>
|
||||
public bool IsSensitiveProperty(string propertyTypeAlias)
|
||||
{
|
||||
MemberTypePropertyProfileAccess propertyProfile;
|
||||
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
|
||||
{
|
||||
return propertyProfile.IsSensitive;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -106,13 +121,14 @@ namespace Umbraco.Core.Models
|
||||
/// <param name="value">Boolean value, true or false</param>
|
||||
public void SetMemberCanEditProperty(string propertyTypeAlias, bool value)
|
||||
{
|
||||
if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias))
|
||||
MemberTypePropertyProfileAccess propertyProfile;
|
||||
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
|
||||
{
|
||||
MemberTypePropertyTypes[propertyTypeAlias].IsEditable = value;
|
||||
propertyProfile.IsEditable = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var tuple = new MemberTypePropertyProfileAccess(false, value);
|
||||
var tuple = new MemberTypePropertyProfileAccess(false, value, false);
|
||||
MemberTypePropertyTypes.Add(propertyTypeAlias, tuple);
|
||||
}
|
||||
}
|
||||
@@ -124,15 +140,35 @@ namespace Umbraco.Core.Models
|
||||
/// <param name="value">Boolean value, true or false</param>
|
||||
public void SetMemberCanViewProperty(string propertyTypeAlias, bool value)
|
||||
{
|
||||
if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias))
|
||||
MemberTypePropertyProfileAccess propertyProfile;
|
||||
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
|
||||
{
|
||||
MemberTypePropertyTypes[propertyTypeAlias].IsVisible = value;
|
||||
propertyProfile.IsVisible = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var tuple = new MemberTypePropertyProfileAccess(value, false);
|
||||
var tuple = new MemberTypePropertyProfileAccess(value, false, false);
|
||||
MemberTypePropertyTypes.Add(propertyTypeAlias, tuple);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a boolean indicating whether a Property is a sensitive value on the Members profile.
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
|
||||
/// <param name="value">Boolean value, true or false</param>
|
||||
public void SetIsSensitiveProperty(string propertyTypeAlias, bool value)
|
||||
{
|
||||
MemberTypePropertyProfileAccess propertyProfile;
|
||||
if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile))
|
||||
{
|
||||
propertyProfile.IsSensitive = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var tuple = new MemberTypePropertyProfileAccess(false, false, true);
|
||||
MemberTypePropertyTypes.Add(propertyTypeAlias, tuple);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,15 @@ namespace Umbraco.Core.Models
|
||||
/// </summary>
|
||||
internal class MemberTypePropertyProfileAccess
|
||||
{
|
||||
public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable)
|
||||
public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable, bool isSenstive)
|
||||
{
|
||||
IsVisible = isVisible;
|
||||
IsEditable = isEditable;
|
||||
IsSensitive = isSenstive;
|
||||
}
|
||||
|
||||
public bool IsVisible { get; set; }
|
||||
public bool IsEditable { get; set; }
|
||||
public bool IsSensitive { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,4 +63,4 @@ namespace Umbraco.Core.Models.Membership
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Models.Membership
|
||||
{
|
||||
public interface IUserGroup : IAggregateRoot
|
||||
public interface IUserGroup : IAggregateRoot, IRememberBeingDirty, ICanBeDirty
|
||||
{
|
||||
string Alias { get; set; }
|
||||
|
||||
@@ -41,4 +41,4 @@ namespace Umbraco.Core.Models.Membership
|
||||
/// </summary>
|
||||
int UserCount { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Models.Membership
|
||||
{
|
||||
internal class MemberExportModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public Guid Key { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Email { get; set; }
|
||||
public List<string> Groups { get; set; }
|
||||
public string ContentTypeAlias { get; set; }
|
||||
public DateTime CreateDate { get; set; }
|
||||
public DateTime UpdateDate { get; set; }
|
||||
public List<MemberExportProperty> Properties { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Models.Membership
|
||||
{
|
||||
internal class MemberExportProperty
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Alias { get; set; }
|
||||
public string Name { get; set; }
|
||||
public object Value { get; set; }
|
||||
public DateTime? CreateDate { get; set; }
|
||||
public DateTime? UpdateDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -706,5 +706,6 @@ namespace Umbraco.Core.Models.Membership
|
||||
return _user.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,4 +152,4 @@ namespace Umbraco.Core.Models.Membership
|
||||
|
||||
public int UserCount { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -436,4 +435,4 @@ namespace Umbraco.Core.Models
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias;
|
||||
|
||||
if (constructors.ContainsKey(typeName))
|
||||
throw new InvalidOperationException(string.Format("More that one type want to be a model for content type {0}.", typeName));
|
||||
throw new InvalidOperationException(string.Format("More than one type wants to be a model for content type {0}.", typeName));
|
||||
|
||||
// should work everywhere, but slow
|
||||
//_constructors[typeName] = constructor;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
|
||||
namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
[TableName(TableName)]
|
||||
[PrimaryKey("id")]
|
||||
[ExplicitColumns]
|
||||
internal class AuditEntryDto
|
||||
{
|
||||
public const string TableName = "umbracoAudit";
|
||||
public const int IpLength = 64;
|
||||
public const int EventTypeLength = 256;
|
||||
public const int DetailsLength = 1024;
|
||||
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn]
|
||||
public int Id { get; set; }
|
||||
|
||||
// there is NO foreign key to the users table here, neither for performing user nor for
|
||||
// affected user, so we can delete users and NOT delete the associated audit trails, and
|
||||
// users can still be identified via the details free-form text fields.
|
||||
|
||||
[Column("performingUserId")]
|
||||
public int PerformingUserId { get; set; }
|
||||
|
||||
[Column("performingDetails")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(DetailsLength)]
|
||||
public string PerformingDetails { get; set; }
|
||||
|
||||
[Column("performingIp")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(IpLength)]
|
||||
public string PerformingIp { get; set; }
|
||||
|
||||
[Column("eventDateUtc")]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime EventDateUtc { get; set; }
|
||||
|
||||
[Column("affectedUserId")]
|
||||
public int AffectedUserId { get; set; }
|
||||
|
||||
[Column("affectedDetails")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(DetailsLength)]
|
||||
public string AffectedDetails { get; set; }
|
||||
|
||||
[Column("eventType")]
|
||||
[Length(EventTypeLength)]
|
||||
public string EventType { get; set; }
|
||||
|
||||
[Column("eventDetails")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(DetailsLength)]
|
||||
public string EventDetails { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
|
||||
namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
[TableName(TableName)]
|
||||
[PrimaryKey("id")]
|
||||
[ExplicitColumns]
|
||||
public class ConsentDto
|
||||
{
|
||||
internal const string TableName = "umbracoConsent";
|
||||
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("current")]
|
||||
public bool Current { get; set; }
|
||||
|
||||
[Column("source")]
|
||||
[Length(512)]
|
||||
public string Source { get; set; }
|
||||
|
||||
[Column("context")]
|
||||
[Length(128)]
|
||||
public string Context { get; set; }
|
||||
|
||||
[Column("action")]
|
||||
[Length(512)]
|
||||
public string Action { get; set; }
|
||||
|
||||
[Column("createDate")]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
[Column("state")]
|
||||
public int State { get; set; }
|
||||
|
||||
[Column("comment")]
|
||||
public string Comment { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -27,5 +27,9 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[Column("viewOnProfile")]
|
||||
[Constraint(Default = "0")]
|
||||
public bool ViewOnProfile { get; set; }
|
||||
|
||||
[Column("isSensitive")]
|
||||
[Constraint(Default = "0")]
|
||||
public bool IsSensitive { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[Column("viewOnProfile")]
|
||||
public bool ViewOnProfile { get; set; }
|
||||
|
||||
[Column("isSensitive")]
|
||||
public bool IsSensitive { get; set; }
|
||||
|
||||
/* cmsDataType */
|
||||
[Column("propertyEditorAlias")]
|
||||
public string PropertyEditorAlias { get; set; }
|
||||
@@ -52,7 +55,8 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[Column("dbType")]
|
||||
public string DbType { get; set; }
|
||||
|
||||
[Column("UniqueID")]
|
||||
[Column("UniqueID")]
|
||||
public Guid UniqueId { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
}
|
||||
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn(IdentitySeed = 5)]
|
||||
[PrimaryKeyColumn(IdentitySeed = 6)]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("userGroupAlias")]
|
||||
@@ -68,4 +68,4 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[ResultColumn]
|
||||
public int UserCount { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,11 +188,11 @@ namespace Umbraco.Core.Models
|
||||
Language,
|
||||
|
||||
/// <summary>
|
||||
/// Document
|
||||
/// Document Blueprint
|
||||
/// </summary>
|
||||
[UmbracoObjectType(Constants.ObjectTypes.DocumentBlueprint, typeof(IContent))]
|
||||
[FriendlyName("DocumentBlueprint")]
|
||||
[UmbracoUdiType(Constants.UdiEntityType.DocumentBluePrint)]
|
||||
[UmbracoUdiType(Constants.UdiEntityType.DocumentBlueprint)]
|
||||
DocumentBlueprint,
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -261,6 +261,16 @@ namespace Umbraco.Core.Models
|
||||
return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this user has access to view sensitive data
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
public static bool HasAccessToSensitiveData(this IUser user)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias);
|
||||
}
|
||||
|
||||
// calc. start nodes, combining groups' and user's, and excluding what's in the bin
|
||||
public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService)
|
||||
{
|
||||
@@ -413,4 +423,4 @@ namespace Umbraco.Core.Models
|
||||
return lsn.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -394,7 +394,7 @@ namespace Umbraco.Core.ObjectResolution
|
||||
/// WARNING! Do not use this unless you know what you are doing, clear all types registered and instances
|
||||
/// created. Typically only used if a resolver is no longer used in an application and memory is to be GC'd
|
||||
/// </summary>
|
||||
internal void ResetCollections()
|
||||
internal virtual void ResetCollections()
|
||||
{
|
||||
using (new WriteLock(_lock))
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence
|
||||
/// it will create one per context, otherwise it will be a global singleton object which is NOT thread safe
|
||||
/// since we need (at least) a new instance of the database object per thread.
|
||||
/// </remarks>
|
||||
internal class DefaultDatabaseFactory : DisposableObject, IDatabaseFactory2
|
||||
internal class DefaultDatabaseFactory : DisposableObjectSlim, IDatabaseFactory2
|
||||
{
|
||||
private readonly string _connectionStringName;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
internal static class AuditEntryFactory
|
||||
{
|
||||
public static IEnumerable<IAuditEntry> BuildEntities(IEnumerable<AuditEntryDto> dtos)
|
||||
{
|
||||
return dtos.Select(BuildEntity).ToList();
|
||||
}
|
||||
|
||||
public static IAuditEntry BuildEntity(AuditEntryDto dto)
|
||||
{
|
||||
var entity = new AuditEntry
|
||||
{
|
||||
Id = dto.Id,
|
||||
PerformingUserId = dto.PerformingUserId,
|
||||
PerformingDetails = dto.PerformingDetails,
|
||||
PerformingIp = dto.PerformingIp,
|
||||
EventDateUtc = dto.EventDateUtc,
|
||||
AffectedUserId = dto.AffectedUserId,
|
||||
AffectedDetails = dto.AffectedDetails,
|
||||
EventType = dto.EventType,
|
||||
EventDetails = dto.EventDetails
|
||||
};
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
// http://issues.umbraco.org/issue/U4-1946
|
||||
entity.ResetDirtyProperties(false);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public static AuditEntryDto BuildDto(IAuditEntry entity)
|
||||
{
|
||||
return new AuditEntryDto
|
||||
{
|
||||
Id = entity.Id,
|
||||
PerformingUserId = entity.PerformingUserId,
|
||||
PerformingDetails = entity.PerformingDetails,
|
||||
PerformingIp = entity.PerformingIp,
|
||||
EventDateUtc = entity.EventDateUtc,
|
||||
AffectedUserId = entity.AffectedUserId,
|
||||
AffectedDetails = entity.AffectedDetails,
|
||||
EventType = entity.EventType,
|
||||
EventDetails = entity.EventDetails
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
internal static class ConsentFactory
|
||||
{
|
||||
public static IEnumerable<IConsent> BuildEntities(IEnumerable<ConsentDto> dtos)
|
||||
{
|
||||
var ix = new Dictionary<string, Consent>();
|
||||
var output = new List<Consent>();
|
||||
|
||||
foreach (var dto in dtos)
|
||||
{
|
||||
var k = dto.Source + "::" + dto.Context + "::" + dto.Action;
|
||||
|
||||
var consent = new Consent
|
||||
{
|
||||
Id = dto.Id,
|
||||
Current = dto.Current,
|
||||
CreateDate = dto.CreateDate,
|
||||
Source = dto.Source,
|
||||
Context = dto.Context,
|
||||
Action = dto.Action,
|
||||
State = (ConsentState) dto.State, // assume value is valid
|
||||
Comment = dto.Comment
|
||||
};
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
// http://issues.umbraco.org/issue/U4-1946
|
||||
consent.ResetDirtyProperties(false);
|
||||
|
||||
if (ix.TryGetValue(k, out var current))
|
||||
{
|
||||
if (current.HistoryInternal == null)
|
||||
current.HistoryInternal = new List<IConsent>();
|
||||
current.HistoryInternal.Add(consent);
|
||||
}
|
||||
else
|
||||
{
|
||||
ix[k] = consent;
|
||||
output.Add(consent);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static ConsentDto BuildDto(IConsent entity)
|
||||
{
|
||||
return new ConsentDto
|
||||
{
|
||||
Id = entity.Id,
|
||||
Current = entity.Current,
|
||||
CreateDate = entity.CreateDate,
|
||||
Source = entity.Source,
|
||||
Context = entity.Context,
|
||||
Action = entity.Action,
|
||||
State = (int) entity.State,
|
||||
Comment = entity.Comment
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,8 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
NodeId = entity.Id,
|
||||
PropertyTypeId = x.Id,
|
||||
CanEdit = memberType.MemberCanEditProperty(x.Alias),
|
||||
ViewOnProfile = memberType.MemberCanViewProperty(x.Alias)
|
||||
ViewOnProfile = memberType.MemberCanViewProperty(x.Alias),
|
||||
IsSensitive = memberType.IsSensitiveProperty(x.Alias)
|
||||
}).ToList();
|
||||
return dtos;
|
||||
}
|
||||
@@ -159,4 +160,4 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,10 +56,10 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
//Add the standard PropertyType to the current list
|
||||
propertyTypes.Add(standardPropertyType.Value);
|
||||
|
||||
//Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType
|
||||
|
||||
//Internal dictionary for adding "MemberCanEdit", "VisibleOnProfile", "IsSensitive" properties to each PropertyType
|
||||
memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key,
|
||||
new MemberTypePropertyProfileAccess(false, false));
|
||||
new MemberTypePropertyProfileAccess(false, false, false));
|
||||
}
|
||||
memberType.NoGroupPropertyTypes = propertyTypes;
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
//Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType
|
||||
memberType.MemberTypePropertyTypes.Add(typeDto.Alias,
|
||||
new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit));
|
||||
new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive));
|
||||
|
||||
var tempGroupDto = groupDto;
|
||||
|
||||
@@ -157,7 +157,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
//Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType
|
||||
memberType.MemberTypePropertyTypes.Add(typeDto.Alias,
|
||||
new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit));
|
||||
new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive));
|
||||
|
||||
//ensures that any built-in membership properties have their correct dbtype assigned no matter
|
||||
//what the underlying data type is
|
||||
@@ -198,4 +198,4 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Mappers
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a mapper for audit entry entities.
|
||||
/// </summary>
|
||||
[MapperFor(typeof(IAuditEntry))]
|
||||
[MapperFor(typeof(AuditEntry))]
|
||||
public sealed class AuditEntryMapper : BaseMapper
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance
|
||||
= new ConcurrentDictionary<string, DtoMapModel>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuditEntryMapper"/> class.
|
||||
/// </summary>
|
||||
public AuditEntryMapper()
|
||||
{
|
||||
// note: why the base ctor does not invoke BuildMap is a mystery to me
|
||||
BuildMap();
|
||||
}
|
||||
|
||||
internal override ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache => PropertyInfoCacheInstance;
|
||||
|
||||
internal override void BuildMap()
|
||||
{
|
||||
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.Id, dto => dto.Id);
|
||||
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.PerformingUserId, dto => dto.PerformingUserId);
|
||||
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.PerformingDetails, dto => dto.PerformingDetails);
|
||||
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.PerformingIp, dto => dto.PerformingIp);
|
||||
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.EventDateUtc, dto => dto.EventDateUtc);
|
||||
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.AffectedUserId, dto => dto.AffectedUserId);
|
||||
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.AffectedDetails, dto => dto.AffectedDetails);
|
||||
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.EventType, dto => dto.EventType);
|
||||
CacheMap<AuditEntry, AuditEntryDto>(entity => entity.EventDetails, dto => dto.EventDetails);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Mappers
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a mapper for consent entities.
|
||||
/// </summary>
|
||||
[MapperFor(typeof(IConsent))]
|
||||
[MapperFor(typeof(Consent))]
|
||||
public sealed class ConsentMapper : BaseMapper
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance
|
||||
= new ConcurrentDictionary<string, DtoMapModel>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConsentMapper"/> class.
|
||||
/// </summary>
|
||||
public ConsentMapper()
|
||||
{
|
||||
// note: why the base ctor does not invoke BuildMap is a mystery to me
|
||||
BuildMap();
|
||||
}
|
||||
|
||||
internal override ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache => PropertyInfoCacheInstance;
|
||||
|
||||
internal override void BuildMap()
|
||||
{
|
||||
CacheMap<Consent, ConsentDto>(entity => entity.Id, dto => dto.Id);
|
||||
CacheMap<Consent, ConsentDto>(entity => entity.Current, dto => dto.Current);
|
||||
CacheMap<Consent, ConsentDto>(entity => entity.CreateDate, dto => dto.CreateDate);
|
||||
CacheMap<Consent, ConsentDto>(entity => entity.Source, dto => dto.Source);
|
||||
CacheMap<Consent, ConsentDto>(entity => entity.Context, dto => dto.Context);
|
||||
CacheMap<Consent, ConsentDto>(entity => entity.Action, dto => dto.Action);
|
||||
CacheMap<Consent, ConsentDto>(entity => entity.State, dto => dto.State);
|
||||
CacheMap<Consent, ConsentDto>(entity => entity.Comment, dto => dto.Comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,11 +178,13 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
_database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" });
|
||||
_database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" });
|
||||
_database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" });
|
||||
_database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" });
|
||||
}
|
||||
|
||||
private void CreateUmbracoUser2UserGroupData()
|
||||
{
|
||||
_database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = 0 });
|
||||
_database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = 0 }); //add admin to admins
|
||||
_database.Insert(new User2UserGroupDto { UserGroupId = 5, UserId = 0 }); //add admin to sensitive data
|
||||
}
|
||||
|
||||
private void CreateUmbracoUserGroup2AppData()
|
||||
@@ -337,4 +339,4 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
_database.Insert("umbracoMigration", "pk", false, dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,9 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
{52, typeof (UserGroup2NodePermissionDto) },
|
||||
{53, typeof (UserGroup2AppDto) },
|
||||
{54, typeof (UserStartNodeDto) },
|
||||
{55, typeof (UserLoginDto)}
|
||||
{55, typeof (UserLoginDto)},
|
||||
{56, typeof (ConsentDto)},
|
||||
{57, typeof (AuditEntryDto)}
|
||||
};
|
||||
#endregion
|
||||
|
||||
@@ -378,4 +380,4 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +154,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
return new Version(7, 7, 0);
|
||||
}
|
||||
|
||||
//if the error is for isSensitive column it must be the previous version to 7.9 since that is when it is added
|
||||
if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMemberType,isSensitive"))))
|
||||
{
|
||||
return new Version(7, 8, 0);
|
||||
}
|
||||
|
||||
return UmbracoVersion.Current;
|
||||
}
|
||||
|
||||
@@ -214,4 +220,4 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
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.9.0", 1, Constants.System.UmbracoMigrationName)]
|
||||
public class AddIsSensitiveMemberTypeColumn : MigrationBase
|
||||
{
|
||||
public AddIsSensitiveMemberTypeColumn(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("cmsMemberType") && x.ColumnName.InvariantEquals("isSensitive")) == false)
|
||||
{
|
||||
Create.Column("isSensitive").OnTable("cmsMemberType").AsBoolean().WithDefaultValue(0).NotNullable();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenNineZero
|
||||
{
|
||||
[Migration("7.9.0", 1, Constants.System.UmbracoMigrationName)]
|
||||
public class AddUmbracoAuditTable : MigrationBase
|
||||
{
|
||||
public AddUmbracoAuditTable(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{ }
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
|
||||
if (tables.InvariantContains(AuditEntryDto.TableName))
|
||||
return;
|
||||
Create.Table<AuditEntryDto>();
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+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.TargetVersionSevenNineZero
|
||||
{
|
||||
[Migration("7.9.0", 1, Constants.System.UmbracoMigrationName)]
|
||||
public class AddUmbracoConsentTable : MigrationBase
|
||||
{
|
||||
public AddUmbracoConsentTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
|
||||
|
||||
if (tables.InvariantContains(ConsentDto.TableName))
|
||||
return;
|
||||
|
||||
Create.Table<ConsentDto>();
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenNineZero
|
||||
{
|
||||
[Migration("7.9.0", 2, Constants.System.UmbracoMigrationName)]
|
||||
public class CreateSensitiveDataUserGroup : MigrationBase
|
||||
{
|
||||
public CreateSensitiveDataUserGroup(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
Execute.Code(database =>
|
||||
{
|
||||
//Don't exeucte if the group is already there
|
||||
var exists = database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoUserGroup WHERE userGroupAlias = @userGroupAlias",
|
||||
new {userGroupAlias = Constants.Security.SensitiveDataGroupAlias });
|
||||
if (exists == 0)
|
||||
{
|
||||
var resultId = database.Insert("umbracoUserGroup", "id", new UserGroupDto { StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" });
|
||||
database.Insert(new User2UserGroupDto { UserGroupId = Convert.ToInt32(resultId), UserId = 0 }); //add admin to sensitive data
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
});
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.Factories;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the PetaPoco implementatino of <see cref="IAuditEntryRepository"/>.
|
||||
/// </summary>
|
||||
internal class AuditEntryRepository : PetaPocoRepositoryBase<int, IAuditEntry>, IAuditEntryRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuditEntryRepository"/> class.
|
||||
/// </summary>
|
||||
public AuditEntryRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
|
||||
: base(work, cache, logger, sqlSyntax)
|
||||
{ }
|
||||
|
||||
protected override Guid NodeObjectTypeId => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IAuditEntry PerformGet(int id)
|
||||
{
|
||||
var sql = new Sql()
|
||||
.Select("*")
|
||||
.From<AuditEntryDto>(SqlSyntax)
|
||||
.Where<AuditEntryDto>(x => x.Id == id, SqlSyntax);
|
||||
|
||||
var dto = Database.FirstOrDefault<AuditEntryDto>(sql);
|
||||
return dto == null ? null : AuditEntryFactory.BuildEntity(dto); }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IEnumerable<IAuditEntry> PerformGetAll(params int[] ids)
|
||||
{
|
||||
if (ids.Length == 0)
|
||||
{
|
||||
var sql = new Sql()
|
||||
.Select("*")
|
||||
.From<AuditEntryDto>(SqlSyntax);
|
||||
|
||||
return Database.Fetch<AuditEntryDto>(sql).Select(AuditEntryFactory.BuildEntity);
|
||||
}
|
||||
|
||||
var entries = new List<IAuditEntry>();
|
||||
|
||||
foreach (var group in ids.InGroupsOf(2000))
|
||||
{
|
||||
var sql = new Sql()
|
||||
.Select("*")
|
||||
.From<AuditEntryDto>(SqlSyntax)
|
||||
.WhereIn<AuditEntryDto>(x => x.Id, group, SqlSyntax);
|
||||
|
||||
entries.AddRange(Database.Fetch<AuditEntryDto>(sql).Select(AuditEntryFactory.BuildEntity));
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IEnumerable<IAuditEntry> PerformGetByQuery(IQuery<IAuditEntry> query)
|
||||
{
|
||||
var sqlClause = GetBaseQuery(false);
|
||||
var translator = new SqlTranslator<IAuditEntry>(sqlClause, query);
|
||||
var sql = translator.Translate();
|
||||
return Database.Fetch<AuditEntryDto>(sql).Select(AuditEntryFactory.BuildEntity);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Sql GetBaseQuery(bool isCount)
|
||||
{
|
||||
return new Sql().Select(isCount ? "COUNT(*)" : "*").From<AuditEntryDto>(SqlSyntax);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string GetBaseWhereClause()
|
||||
{
|
||||
return $"{AuditEntryDto.TableName}.id = @Id";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IEnumerable<string> GetDeleteClauses()
|
||||
{
|
||||
throw new NotSupportedException("Audit entries cannot be deleted.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PersistNewItem(IAuditEntry entity)
|
||||
{
|
||||
((Entity) entity).AddingEntity();
|
||||
|
||||
var dto = AuditEntryFactory.BuildDto(entity);
|
||||
Database.Insert(dto);
|
||||
entity.Id = dto.Id;
|
||||
entity.ResetDirtyProperties();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PersistUpdatedItem(IAuditEntry entity)
|
||||
{
|
||||
throw new NotSupportedException("Audit entries cannot be updated.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IAuditEntry> GetPage(long pageIndex, int pageCount, out long records)
|
||||
{
|
||||
var sql = new Sql()
|
||||
.Select("*")
|
||||
.From<AuditEntryDto>(SqlSyntax)
|
||||
.OrderByDescending<AuditEntryDto>(x => x.EventDateUtc, SqlSyntax);
|
||||
|
||||
var page = Database.Page<AuditEntryDto>(pageIndex + 1, pageCount, sql);
|
||||
records = page.TotalItems;
|
||||
return page.Items.Select(AuditEntryFactory.BuildEntity);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAvailable()
|
||||
{
|
||||
var tables = SqlSyntax.GetTablesInSchema(Database).ToArray();
|
||||
return tables.InvariantContains(AuditEntryDto.TableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.Factories;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the PetaPoco implementation of <see cref="IConsentRepository"/>.
|
||||
/// </summary>
|
||||
internal class ConsentRepository : PetaPocoRepositoryBase<int, IConsent>, IConsentRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConsentRepository"/> class.
|
||||
/// </summary>
|
||||
public ConsentRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
|
||||
: base(work, cache, logger, sqlSyntax)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Guid NodeObjectTypeId => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ClearCurrent(string source, string context, string action)
|
||||
{
|
||||
var sql = new Sql($@"UPDATE {SqlSyntax.GetQuotedTableName(ConsentDto.TableName)}
|
||||
SET {SqlSyntax.GetQuotedColumnName("current")} = @0
|
||||
WHERE {SqlSyntax.GetQuotedColumnName("source")} = @1 AND {SqlSyntax.GetQuotedColumnName("context")} = @2 AND {SqlSyntax.GetQuotedColumnName("action")} = @3
|
||||
AND {SqlSyntax.GetQuotedColumnName("current")} <> @0 ", false, source, context, action);
|
||||
|
||||
Database.Execute(sql);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IConsent PerformGet(int id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IEnumerable<IConsent> PerformGetAll(params int[] ids)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IEnumerable<IConsent> PerformGetByQuery(IQuery<IConsent> query)
|
||||
{
|
||||
var sqlClause = new Sql().Select("*").From<ConsentDto>(SqlSyntax);
|
||||
var translator = new SqlTranslator<IConsent>(sqlClause, query);
|
||||
var sql = translator.Translate().OrderByDescending<ConsentDto>(x => x.CreateDate, SqlSyntax);
|
||||
return ConsentFactory.BuildEntities(Database.Fetch<ConsentDto>(sql));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Sql GetBaseQuery(bool isCount)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string GetBaseWhereClause()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IEnumerable<string> GetDeleteClauses()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PersistNewItem(IConsent entity)
|
||||
{
|
||||
((Entity) entity).AddingEntity();
|
||||
|
||||
var dto = ConsentFactory.BuildDto(entity);
|
||||
Database.Insert(dto);
|
||||
entity.Id = dto.Id;
|
||||
entity.ResetDirtyProperties();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PersistUpdatedItem(IConsent entity)
|
||||
{
|
||||
((Entity) entity).UpdatingEntity();
|
||||
|
||||
var dto = ConsentFactory.BuildDto(entity);
|
||||
Database.Update(dto);
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
IsolatedCache.ClearCacheItem(GetCacheIdKey<IConsent>(entity.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <remarks>
|
||||
/// This is limited to objects that are based in the umbracoNode-table.
|
||||
/// </remarks>
|
||||
internal class EntityRepository : DisposableObject, IEntityRepository
|
||||
internal class EntityRepository : DisposableObjectSlim, IEntityRepository
|
||||
{
|
||||
private readonly IDatabaseUnitOfWork _work;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
internal abstract class FileRepository<TId, TEntity> : DisposableObject, IUnitOfWorkRepository, IRepository<TId, TEntity>
|
||||
internal abstract class FileRepository<TId, TEntity> : DisposableObjectSlim, IUnitOfWorkRepository, IRepository<TId, TEntity>
|
||||
where TEntity : IFile
|
||||
{
|
||||
private IUnitOfWork _work;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a repository for <see cref="IAuditEntry"/> entities.
|
||||
/// </summary>
|
||||
public interface IAuditEntryRepository : IRepositoryQueryable<int, IAuditEntry>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a page of entries.
|
||||
/// </summary>
|
||||
IEnumerable<IAuditEntry> GetPage(long pageIndex, int pageCount, out long records);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the repository is available.
|
||||
/// </summary>
|
||||
/// <remarks>During an upgrade, the repository may not be available, until the table has been created.</remarks>
|
||||
bool IsAvailable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a repository for <see cref="IConsent"/> entities.
|
||||
/// </summary>
|
||||
public interface IConsentRepository : IRepositoryQueryable<int, IConsent>
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears the current flag.
|
||||
/// </summary>
|
||||
void ClearCurrent(string source, string context, string action);
|
||||
}
|
||||
}
|
||||
@@ -39,5 +39,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
void AssignRoles(int[] memberIds, string[] roleNames);
|
||||
|
||||
void DissociateRoles(int[] memberIds, string[] roleNames);
|
||||
|
||||
int[] GetMemberIds(string[] names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,24 +184,23 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
public IEnumerable<IMemberGroup> GetMemberGroupsForMember(string username)
|
||||
{
|
||||
var sql = new Sql()
|
||||
var sql = new Sql()
|
||||
.Select("un.*")
|
||||
.From("umbracoNode AS un")
|
||||
.InnerJoin("cmsMember2MemberGroup")
|
||||
.On("un.id = cmsMember2MemberGroup.MemberGroup")
|
||||
.LeftJoin("(SELECT umbracoNode.id, cmsMember.LoginName FROM umbracoNode INNER JOIN cmsMember ON umbracoNode.id = cmsMember.nodeId) AS member")
|
||||
.On("member.id = cmsMember2MemberGroup.Member")
|
||||
.Where("un.nodeObjectType=@objectType", new {objectType = NodeObjectTypeId })
|
||||
.Where("member.LoginName=@loginName", new {loginName = username});
|
||||
.On("cmsMember2MemberGroup.MemberGroup = un.id")
|
||||
.InnerJoin("cmsMember")
|
||||
.On("cmsMember.nodeId = cmsMember2MemberGroup.Member")
|
||||
.Where("un.nodeObjectType=@objectType", new { objectType = NodeObjectTypeId })
|
||||
.Where("cmsMember.LoginName=@loginName", new { loginName = username });
|
||||
|
||||
return Database.Fetch<NodeDto>(sql)
|
||||
.DistinctBy(dto => dto.NodeId)
|
||||
.Select(x => _modelFactory.BuildEntity(x));
|
||||
}
|
||||
|
||||
public void AssignRoles(string[] usernames, string[] roleNames)
|
||||
public int[] GetMemberIds(string[] names)
|
||||
{
|
||||
//first get the member ids based on the usernames
|
||||
var memberSql = new Sql();
|
||||
var memberObjectType = new Guid(Constants.ObjectTypes.Member);
|
||||
memberSql.Select("umbracoNode.id")
|
||||
@@ -209,25 +208,19 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
.InnerJoin<MemberDto>()
|
||||
.On<NodeDto, MemberDto>(dto => dto.NodeId, dto => dto.NodeId)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == memberObjectType)
|
||||
.Where("cmsMember.LoginName in (@usernames)", new { usernames = usernames });
|
||||
var memberIds = Database.Fetch<int>(memberSql).ToArray();
|
||||
.Where("cmsMember.LoginName in (@names)", new { names });
|
||||
return Database.Fetch<int>(memberSql).ToArray();
|
||||
}
|
||||
|
||||
public void AssignRoles(string[] usernames, string[] roleNames)
|
||||
{
|
||||
var memberIds = GetMemberIds(usernames);
|
||||
AssignRolesInternal(memberIds, roleNames);
|
||||
}
|
||||
|
||||
public void DissociateRoles(string[] usernames, string[] roleNames)
|
||||
{
|
||||
//first get the member ids based on the usernames
|
||||
var memberSql = new Sql();
|
||||
var memberObjectType = new Guid(Constants.ObjectTypes.Member);
|
||||
memberSql.Select("umbracoNode.id")
|
||||
.From<NodeDto>()
|
||||
.InnerJoin<MemberDto>()
|
||||
.On<NodeDto, MemberDto>(dto => dto.NodeId, dto => dto.NodeId)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == memberObjectType)
|
||||
.Where("cmsMember.LoginName in (@usernames)", new { usernames = usernames });
|
||||
var memberIds = Database.Fetch<int>(memberSql).ToArray();
|
||||
|
||||
var memberIds = GetMemberIds(usernames);
|
||||
DissociateRolesInternal(memberIds, roleNames);
|
||||
}
|
||||
|
||||
@@ -337,4 +330,4 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// </summary>
|
||||
internal static event TypedEventHandler<IMemberGroupRepository, SaveEventArgs<IMemberGroup>> SavedMemberGroup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
sql.Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.id AS PropertyTypeId", "cmsPropertyType.Alias",
|
||||
"cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.mandatory", "cmsPropertyType.UniqueID",
|
||||
"cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder",
|
||||
"cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile",
|
||||
"cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId",
|
||||
"cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", "cmsMemberType.isSensitive",
|
||||
"cmsDataType.propertyEditorAlias", "cmsDataType.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId",
|
||||
"cmsPropertyTypeGroup.text AS PropertyGroupName", "cmsPropertyTypeGroup.uniqueID AS PropertyGroupUniqueID",
|
||||
"cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder", "cmsPropertyTypeGroup.contenttypeNodeId")
|
||||
@@ -362,4 +363,4 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return Attempt<Guid>.Fail(propertyEditor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
internal abstract class RepositoryBase : DisposableObject
|
||||
internal abstract class RepositoryBase : DisposableObjectSlim
|
||||
{
|
||||
private readonly IScopeUnitOfWork _work;
|
||||
private readonly CacheHelper _globalCache;
|
||||
@@ -308,6 +308,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
public virtual void PersistNewItem(IEntity entity)
|
||||
{
|
||||
CachePolicy.Create((TEntity) entity, PersistNewItem);
|
||||
|
||||
//TODO: In v8 we should automatically reset dirty properties so they don't have to be manually reset in all of the implemented repositories
|
||||
//if (entity is ICanBeDirty dirty) dirty.ResetDirtyProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -317,6 +320,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
public virtual void PersistUpdatedItem(IEntity entity)
|
||||
{
|
||||
CachePolicy.Update((TEntity) entity, PersistUpdatedItem);
|
||||
//TODO: In v8 we should automatically reset dirty properties so they don't have to be manually reset in all of the implemented repositories
|
||||
//if (entity is ICanBeDirty dirty) dirty.ResetDirtyProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -343,4 +348,4 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
UnitOfWork.DisposeIfDisposable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,12 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
var name = Name;
|
||||
|
||||
// cater for instances where node has no name.
|
||||
if(string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return _numPos;
|
||||
}
|
||||
|
||||
if (name[name.Length - 1] != ')')
|
||||
return _numPos = -1;
|
||||
|
||||
@@ -106,7 +112,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
return uniqueing ? string.Concat(nodeName, " (", uniqueNumber.ToString(), ")") : nodeName;
|
||||
return uniqueing || string.IsNullOrWhiteSpace(nodeName) ? string.Concat(nodeName, " (", uniqueNumber.ToString(), ")") : nodeName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,7 +285,7 @@ umbracoUserGroup.userGroupName, umbracoUserGroup2App.app, umbracoUserGroup2App.u
|
||||
|
||||
protected override void PersistNewItem(IUserGroup entity)
|
||||
{
|
||||
((UserGroup)entity).AddingEntity();
|
||||
((UserGroup)entity).AddingEntity();
|
||||
|
||||
var userGroupDto = UserGroupFactory.BuildDto(entity);
|
||||
|
||||
@@ -293,6 +293,8 @@ umbracoUserGroup.userGroupName, umbracoUserGroup2App.app, umbracoUserGroup2App.u
|
||||
entity.Id = id;
|
||||
|
||||
PersistAllowedSections(entity);
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
}
|
||||
|
||||
protected override void PersistUpdatedItem(IUserGroup entity)
|
||||
@@ -304,6 +306,8 @@ umbracoUserGroup.userGroupName, umbracoUserGroup2App.app, umbracoUserGroup2App.u
|
||||
Database.Update(userGroupDto);
|
||||
|
||||
PersistAllowedSections(entity);
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
}
|
||||
|
||||
private void PersistAllowedSections(IUserGroup entity)
|
||||
@@ -456,4 +460,4 @@ umbracoUserGroup.userGroupName, umbracoUserGroup2App.app, umbracoUserGroup2App.u
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,11 +192,11 @@ ORDER BY colName";
|
||||
|
||||
return new Dictionary<UserState, int>
|
||||
{
|
||||
{UserState.All, result[0].num},
|
||||
{UserState.Active, result[1].num},
|
||||
{UserState.Disabled, result[2].num},
|
||||
{UserState.LockedOut, result[3].num},
|
||||
{UserState.Invited, result[4].num}
|
||||
{UserState.All, (int)result[0].num},
|
||||
{UserState.Active, (int)result[1].num},
|
||||
{UserState.Disabled, (int)result[2].num},
|
||||
{UserState.LockedOut, (int)result[3].num},
|
||||
{UserState.Invited, (int)result[4].num}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -54,8 +54,8 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
//if the result is already a DeepCloneRuntimeCacheProvider then return it, otherwise
|
||||
//wrap the result with a DeepCloneRuntimeCacheProvider
|
||||
return cache is DeepCloneRuntimeCacheProvider
|
||||
? cache
|
||||
return cache is DeepCloneRuntimeCacheProvider
|
||||
? cache
|
||||
: new DeepCloneRuntimeCacheProvider(cache);
|
||||
};
|
||||
}
|
||||
@@ -158,11 +158,11 @@ namespace Umbraco.Core.Persistence
|
||||
CreateTemplateRepository(uow),
|
||||
CreateTagRepository(uow),
|
||||
_settings.Content)
|
||||
{
|
||||
{
|
||||
//duplicates are allowed
|
||||
EnsureUniqueNaming = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IContentTypeRepository CreateContentTypeRepository(IScopeUnitOfWork uow)
|
||||
{
|
||||
@@ -319,13 +319,13 @@ namespace Umbraco.Core.Persistence
|
||||
var userMembershipProvider = MembershipProviderExtensions.GetUsersMembershipProvider();
|
||||
var passwordConfig = userMembershipProvider == null || userMembershipProvider.PasswordFormat != MembershipPasswordFormat.Hashed
|
||||
? null
|
||||
: new System.Collections.Generic.Dictionary<string, string> {{"hashAlgorithm", Membership.HashAlgorithmType}};
|
||||
: new System.Collections.Generic.Dictionary<string, string> {{"hashAlgorithm", Membership.HashAlgorithmType}};
|
||||
|
||||
return new UserRepository(
|
||||
uow,
|
||||
//Need to cache users - we look up user information more than anything in the back office!
|
||||
_cacheHelper,
|
||||
_logger,
|
||||
_logger,
|
||||
_sqlSyntax,
|
||||
passwordConfig);
|
||||
}
|
||||
@@ -397,5 +397,15 @@ namespace Umbraco.Core.Persistence
|
||||
_logger,
|
||||
_sqlSyntax);
|
||||
}
|
||||
|
||||
public IConsentRepository CreateConsentRepository(IScopeUnitOfWork uow)
|
||||
{
|
||||
return new ConsentRepository(uow, _cacheHelper, _logger, _sqlSyntax);
|
||||
}
|
||||
|
||||
public IAuditEntryRepository CreateAuditEntryRepository(IScopeUnitOfWork uow)
|
||||
{
|
||||
return new AuditEntryRepository(uow, _cacheHelper, _logger, _sqlSyntax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork
|
||||
/// <summary>
|
||||
/// Represents a scoped unit of work.
|
||||
/// </summary>
|
||||
internal class ScopeUnitOfWork : DisposableObject, IScopeUnitOfWork
|
||||
internal class ScopeUnitOfWork : DisposableObjectSlim, IScopeUnitOfWork
|
||||
{
|
||||
private readonly Queue<Operation> _operations = new Queue<Operation>();
|
||||
private readonly IsolationLevel _isolationLevel;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Security.Permissions;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Umbraco.Core")]
|
||||
@@ -12,8 +10,8 @@ using System.Security.Permissions;
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyProduct("Umbraco CMS")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
@@ -50,6 +48,8 @@ using System.Security.Permissions;
|
||||
[assembly: InternalsVisibleTo("Umbraco.Forms.Core.Providers")]
|
||||
[assembly: InternalsVisibleTo("Umbraco.Forms.Web")]
|
||||
|
||||
[assembly: InternalsVisibleTo("Umbraco.Headless")]
|
||||
|
||||
//allow this to be mocked in our unit tests
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
public PropertyEditorResolver(IServiceProvider serviceProvider, ILogger logger, Func<IEnumerable<Type>> typeListProducerList, IRuntimeCacheProvider runtimeCache)
|
||||
: base(serviceProvider, logger, typeListProducerList, ObjectLifetimeScope.Application)
|
||||
{
|
||||
var builder = new ManifestBuilder(
|
||||
var builder = new ManifestBuilder(
|
||||
runtimeCache,
|
||||
new ManifestParser(new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), runtimeCache));
|
||||
|
||||
@@ -49,7 +49,10 @@ namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
var nestedContentEditorFromPackage = editors.FirstOrDefault(x => x.Alias == "Our.Umbraco.NestedContent");
|
||||
if (nestedContentEditorFromPackage != null)
|
||||
{
|
||||
nestedContentEditorFromPackage.Name = "(Obsolete) " + nestedContentEditorFromPackage.Name;
|
||||
nestedContentEditorFromPackage.IsDeprecated = true;
|
||||
}
|
||||
return editors;
|
||||
|
||||
}
|
||||
@@ -74,4 +77,4 @@ namespace Umbraco.Core.PropertyEditors
|
||||
return PropertyEditors.SingleOrDefault(x => x.Alias == alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Manifest;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -176,6 +176,12 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// <returns></returns>
|
||||
internal Attempt<object> TryConvertValueToCrlType(object value)
|
||||
{
|
||||
var jv = value as JValue;
|
||||
if (jv != null)
|
||||
{
|
||||
value = value.ToString();
|
||||
}
|
||||
|
||||
//this is a custom check to avoid any errors, if it's a string and it's empty just make it null
|
||||
var s = value as string;
|
||||
if (s != null)
|
||||
@@ -391,4 +397,4 @@ namespace Umbraco.Core.PropertyEditors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user