Compare commits

..

439 Commits

Author SHA1 Message Date
Sebastiaan Janssen 52dce66598 Bump version 2017-02-21 08:49:34 +01:00
Sebastiaan Janssen acf10eeef0 U4-9532 logic in ContentRepository.ClearPublished is inefficient - cherry picked from 7.6.0 2017-02-16 08:11:35 +01:00
Sebastiaan Janssen 759e444624 Merge pull request #1467 from ArnoldV/dev-v7
U4-8990 Updated Dutch Language File
2017-02-15 22:39:44 +01:00
Sebastiaan Janssen a68f4e284a Merge pull request #1752 from umbraco/temp-U4-9517
U4-9517 BackOfficeSignInManager doesn't automatically keep LastLoginD…
2017-02-13 10:38:41 +01:00
Shannon d554417fde U4-9517 BackOfficeSignInManager doesn't automatically keep LastLoginDate set on users 2017-02-13 16:47:04 +11:00
Claus eedcfc122b Merge pull request #1747 from umbraco/temp-U4-9513
U4-9513 Empty recycle bin SQL logic can SQL to timeout due to query i…
2017-02-10 11:42:14 +01:00
Claus 8cf3110708 fixing typo and missing file ref in csproj. 2017-02-10 11:41:46 +01:00
Shannon dbe489c0f2 U4-9513 Empty recycle bin SQL logic can SQL to timeout due to query inefficiencies 2017-02-10 12:27:50 +11:00
Sebastiaan Janssen a7e28a97d9 Merge pull request #1715 from umbraco/temp-U4-9450
U4-9450 Paged data queries return all property data in the entire dat…
2017-02-09 21:02:15 +01:00
Sebastiaan Janssen ade3c4ee6c Merge pull request #1732 from rsoeteman/U4-9493
U4 9493
2017-02-09 14:26:44 +01:00
Arnold Visser 7da7c1e5a1 Addes all new/missing fields since last edit
Checked against the English version
2017-02-09 13:47:31 +01:00
Shannon Deminick 55098deac0 Merge pull request #1728 from umbraco/temp-u4-9487
U4-9487 - optimize initialization of xml nodes
2017-02-09 16:56:05 +11:00
Shannon 58c5618d30 Adds benchmarks for XML perf testing - had to make the init xml code static with out params to test but that's ok. 2017-02-09 16:42:59 +11:00
Shannon d0cbeb4a0a Better optimized initialization of xml nodes with less allocations 2017-02-09 15:29:38 +11:00
Mikkel Holck Madsen a8914dcfdb Merge pull request #1740 from umbraco/temp-U4-9458
Adds unit tests for U4-9458 UmbracoExamine performance optimizations
2017-02-08 15:12:07 +01:00
Sebastiaan Janssen 73e219b23c Fix unit tests 2017-02-07 16:24:34 +01:00
Sebastiaan Janssen 07e0de5824 Adds unit tests for U4-9458 UmbracoExamine performance optimizations 2017-02-07 16:20:40 +01:00
Sebastiaan Janssen 389b29599b U4-9495 ImageProcessor high CPU usage when trimCache=true 2017-02-07 10:29:11 +01:00
Shannon f816a63b54 Ok, reverting the correct commit this time: Revert "Fixes database context issue during startup when there is no db configured" 2017-02-07 12:49:26 +11:00
Shannon fa8f3985b8 Fixes database context issue during startup when there is no db configured 2017-02-07 12:33:37 +11:00
Mikkel Holck Madsen 175c785705 Merge pull request #1722 from umbraco/temp-U4-9458
Changes IndexFieldPolicies to be a StaticFieldCollection which is a k…
2017-02-06 21:45:54 +01:00
Mikkel Holck Madsen 72aebd3ecf Merge branch 'dev-v7' into temp-U4-9458 2017-02-06 21:44:26 +01:00
Mikkel Holck Madsen 802cbb82c4 Merge pull request #1723 from umbraco/temp-U4-9448
U4-9448 Slave Front End server requires write access to database when…
2017-02-06 15:01:30 +01:00
Mikkel Holck Madsen 42f5507fe8 Merge pull request #1734 from umbraco/temp-U4-9492
U4-9492 Update to latest ImageProcessor
2017-02-06 14:39:01 +01:00
Sebastiaan Janssen 5a8e37f5b4 U4-9492 Update to latest ImageProcessor 2017-02-06 13:46:10 +01:00
Richard Soeteman a3c7b1fcfe Fixed issue U4-9494 2017-02-06 11:51:44 +01:00
Richard Soeteman 02785eea42 Fixed issue U4-9493 2017-02-06 11:26:58 +01:00
Shannon Deminick 3c7d3b2f63 Merge pull request #1724 from umbraco/temp-installer-url-update
Update NewInstallStep.cs
2017-02-06 19:42:23 +11:00
Stephan 49f3979d18 U4-9487 - optimize initialization of xml nodes 2017-02-03 19:28:43 +01:00
Shannon d598fdce54 Updates to latest Examine release 2017-02-03 12:03:56 +11:00
hartvig 32892cf5e0 Update NewInstallStep.cs 2017-02-02 10:05:14 +01:00
Shannon 32d757b13c Updates the ContentRepository to not fetch the published version info in the same query when returning a collection of documents, instead we fetch the published version information in a separate query 2017-02-02 17:05:33 +11:00
Shannon 882d5ec7f5 missing null check 2017-02-02 16:02:29 +11:00
Shannon ce69497a68 Update code to have zero breaking changes for old public APIs 2017-02-02 14:16:39 +11:00
Shannon 1d43cf491b U4-9448 Slave Front End server requires write access to database when master performs unpublish 2017-02-02 14:09:49 +11:00
Shannon 27283754c0 Changes IndexFieldPolicies to be a StaticFieldCollection which is a keyed collection instead of using Linq and iterating to find the values in critical code 2017-02-01 16:48:09 +11:00
Shannon 5b2212829c Small performance change which makes a fairly large impact when rebuilding very large indexes. 2017-02-01 13:02:22 +11:00
Morten Christensen 546209ddb5 Merge pull request #1716 from umbraco/temp-U4-9451
U4-9451 Package installation custom user control is not displayed aft…
2017-01-31 12:24:52 +01:00
Shannon 86b2dac231 Needed to change the 2nd query to use a new PagingSqlQuery object due to the way that < SQL 2012 formats it's paging query which is just not compatible with how we were parsing the queries for properties... i don't actually think it was working for a long tme. 2017-01-31 17:20:44 +11:00
Shannon 54bda6f12a U4-9451 Package installation custom user control is not displayed after a package is installed 2017-01-31 15:40:02 +11:00
Shannon 2f67a485aa Ensures that the sqlIds query is used for paging when processing members/media 2017-01-31 14:52:48 +11:00
Shannon f192f241cf U4-9450 Paged data queries return all property data in the entire database, not just for the paged subset 2017-01-31 13:48:47 +11:00
Sebastiaan Janssen 690af8e494 Merge pull request #1711 from umbraco/temp-U4-9444
U4-9444 SessionState is not available when rendering macro contents i…
2017-01-30 10:00:40 +01:00
Shannon 9d06ce53a4 U4-9444 SessionState is not available when rendering macro contents in the Rich Text Editor 2017-01-30 13:22:47 +11:00
Claus daa80a7bf6 Merge pull request #1709 from umbraco/temp-U4-9435-2
U4-9435 Fix breaking change made in U4-9312
2017-01-27 09:05:47 +01:00
Shannon 8152e7cb92 Doh, forgot a file change for tests - fixes build 2017-01-27 16:55:29 +11:00
Shannon Deminick adccb4161e Merge pull request #1707 from umbraco/temp-u4-9438
U4-9438 - fix nasty issue with reading properties in VersionableRepos…
2017-01-27 16:52:10 +11:00
Shannon 72a0187295 Writes unit test to confirm the correct property data iteration in GetPropertyCollection 2017-01-27 16:50:44 +11:00
Shannon 24d6287918 Adds string overloads just in case 2017-01-27 16:21:01 +11:00
Shannon 55900a3464 Updates MediaController and MediaTypeController to have the same method signature supporting both INT and GUID with a custom action selector to handle the ambiguity 2017-01-27 16:09:35 +11:00
Sebastiaan Janssen 36e9bb4d78 Merge pull request #1705 from umbraco/temp-v758-recyclebin-fix
Fixes U4-9437
2017-01-26 17:31:31 +01:00
Sebastiaan Janssen 639d04381e Bump version 2017-01-26 14:48:19 +01:00
Stephan 7f905bce0e U4-9438 - fix nasty issue with reading properties in VersionableRepositoryBase 2017-01-26 11:49:52 +01:00
Niels Hartvig 205ea22daa Fixes U4-9437 2017-01-25 17:34:34 +01:00
Shannon c7b505fd90 Updates the paging logic so that the ContentRepository uses 2x queries: A full query and a query for getting Ids. This is important because the query for fetching ids is used for paging and for getting property data and it can be much much faster than the full query which was previously used for both. There's not really any changes to the media/members respositories since their full queries don't have certain outer joins that make them run really slow 2017-01-24 16:04:26 +11:00
Sebastiaan Janssen c5f7ac18fd Merge pull request #1702 from umbraco/temp-U4-9427
Fixes: U4-9427 Unable to install local packages in various browsers
2017-01-23 16:38:11 +01:00
Sebastiaan Janssen 10b8fd9fb9 Merge pull request #1701 from umbraco/temp-U4-9420
U4-9420 Re-indexing Examine Indexes after Upgrade to 7.5.7 from 7.3.4 takes minutes when it used to take seconds
2017-01-23 16:18:45 +01:00
Mads Rasmussen 24c053d671 Fixes: U4-9427 Unable to install local packages in various browsers 2017-01-23 16:11:09 +01:00
Shannon 44fc8be49e Changes back to "path" default sorting but updates the UmbracoContentIndexer to ensure it's sorted by umbracoNode.id 2017-01-24 00:35:17 +11:00
Shannon 2a4e73c650 Reduced allocations for the Media/Content/Property Factories since we don't need to create new objects every time we want to map values.
Changes default sort order for paging from Path to umbracoNode.id since we have no index on Path and it doesn't make a lot of sense.
Fixes obsolete warnings for various Sql usages.
Reduces the amount of DeepClone calls required when looking up a content type for a content item, even though these are cached they are still deep cloned out of the cache.
Fixes the main issue of having nearly 100,000 rows of unsorted property data and then having to query those rows for every document being built, the re-iteration of these rows causes a lot of overhead and is unecessary, instead we ensure the property data set and the document data set is sorted by node id, then use a stored index to continue looking up the property data for the next content item found.
2017-01-23 00:40:24 +11:00
Sebastiaan Janssen f0a540eba2 Merge branch 'master-v7' into dev-v7 2017-01-20 09:55:50 +01:00
Sebastiaan Janssen 12922f8413 No need to warn if attribute is not present
Adds some logging about indexing being done.. the number of committed items seems to differ a little from the number of items in the index, not sure wh
2017-01-20 09:55:41 +01:00
Sebastiaan Janssen 85f054e3c1 No need to warn if attribute is not present
Adds some logging about indexing being done.. the number of committed items seems to differ a little from the number of items in the index, not sure wh
2017-01-20 09:54:59 +01:00
Sebastiaan Janssen 470bf86ec0 Merge branch 'dev-v7' into master-v7 2017-01-19 11:51:05 +01:00
Morten Christensen 9367f61952 Merge pull request #1697 from umbraco/temp-U4-9414
U4-9414 EntityContainer repository PerformGetAll query returns nothing
2017-01-19 11:49:19 +01:00
Sebastiaan Janssen 3cabd8c318 Merge branch 'dev-v7' into master-v7 2017-01-19 11:40:11 +01:00
Claus 4119380af5 U4-9414 EntityContainer repository PerformGetAll query returns nothing 2017-01-19 11:39:09 +01:00
Sebastiaan Janssen fc3b08e374 Merge pull request #1626 from Mark320/U4-9192
U4-9192 Add jQuery migrate for user controls used in tabs
2017-01-19 11:35:44 +01:00
Sebastiaan Janssen a57abce96e Manually applying https://github.com/umbraco/Umbraco-CMS/pull/1688 2017-01-19 11:30:14 +01:00
Sebastiaan Janssen b260f6c6de Merge pull request #1679 from umbraco/U-9333
Add support for GUIDs in media and content pickers
2017-01-19 11:25:34 +01:00
Shannon c96e017e65 Fixes PocoToSqlExpressionVisitor (oops), fixes examine tests by using the correct usings and NRT indexer/searcher to avoid waiting on thread timers 2017-01-19 14:00:33 +11:00
Shannon 4f596d20a9 refactors the fix for U4-9398 so it's not iterating multiple times over the descendants Enumerable 2017-01-19 13:13:49 +11:00
Shannon Deminick 343e462473 Merge pull request #1695 from mahgo/PerformIndexAll_paging_fix
Fixed UmbracoContentIndexer's PerformIndexAll SupportUnpublishedConte…
2017-01-19 13:10:30 +11:00
Shannon db414e8045 Fixes issue with implicitly non-published items getting into the index, fixes issue with sql syntax columns (i.e for mysql) for queries that query the cmsContentXml table 2017-01-19 12:51:56 +11:00
Sebastiaan Janssen 54e460cf5b U4-9392 Upgrade ImageProcessor with cache header and high CPU fix
U4-9393 Upgrade to Examine 0.1.80
2017-01-18 11:47:23 +01:00
Aaron Sturmfels ba64768d8d Fixed UmbracoContentIndexer's PerformIndexAll SupportUnpublishedContent = true paging breaking out of the loop early if filtering by node types. 2017-01-18 21:12:30 +11:00
Warren Buckley 6710ac8c85 Fix up camelCase method name to GetChildren 2017-01-18 09:43:21 +00:00
Warren Buckley 1f98b76da0 On app.authenticated JS event - we clear out LocalStorage values in case a different user is logging in 2017-01-18 09:34:02 +00:00
Sebastiaan Janssen 519eb9d641 Merge pull request #1694 from umbraco/temp-U4-9395
Temp u4 9395
2017-01-18 08:56:08 +01:00
Sebastiaan Janssen 931db6f0b1 U4-9395 - Handle errors with non-existing fields and invalide JSON data 2017-01-17 16:47:54 +01:00
Sebastiaan Janssen f24c56d1ba Merge pull request #1692 from umbraco/temp-U4-9395
U4-9395 When rebuilding content indexes that don't support unpublishe…
2017-01-17 11:45:17 +01:00
Sebastiaan Janssen 44608c3122 Merge branch 'dev-v7' into temp-U4-9395 2017-01-17 10:46:04 +01:00
Stephan 90f2fc4cd9 Merge pull request #1686 from umbraco/temp-U4-9388-2
Finds all other instances where a reader would be executed inside ano…
2017-01-17 08:18:54 +01:00
Shannon 24c7dee5d0 U4-9395 When rebuilding content indexes that don't support unpublished content and member indexes, use the cmsContentXml table as the data source 2017-01-17 17:57:49 +11:00
Shannon Deminick 12417c0c48 Merge pull request #1691 from umbraco/temp-UAASSCRUM-712
adds more logging for when indexes are rebuilt
2017-01-17 16:40:02 +11:00
Shannon Deminick d4919ff8c4 Merge pull request #1683 from umbraco/temp-U4-9371
U4-9371 Examine indexes inherited unpublished nodes on index rebuild …
2017-01-17 16:34:31 +11:00
Shannon b2acb97639 adds stopwatch timer to logging 2017-01-17 16:29:30 +11:00
Shannon 28debbda1b adds more logging for when indexes are rebuilt 2017-01-17 16:21:43 +11:00
Niels Hartvig c0a2a78f3d Fixes issue with entities not being returned from Keys (Guids) 2017-01-16 19:13:05 +01:00
Sebastiaan Janssen 3f7bfe0b3a Merge pull request #1685 from jamiepollock/U4-9387
U4-9387: Umbraco v7.5.x: Package Icon doesn't appear on Installed Packages screen when an icon URL is populated
2017-01-16 12:58:39 +01:00
Claus e0d281bb4b Merge pull request #1616 from abryukhov/patch-6
Update UI lang file for ru-ru
2017-01-16 11:26:23 +01:00
Sebastiaan Janssen b7d37af933 Bumps version 2017-01-16 11:04:28 +01:00
Claus 3f9cfbb422 Merge pull request #1642 from readingdancer/ChrisH-TypoFix
Small English grammar error.
2017-01-16 08:12:59 +01:00
Claus 2f3d926c08 Merge pull request #1638 from enkelmedia/dev-v7
Fixed english typo for modelsbuilder
2017-01-16 08:11:45 +01:00
Claus 594c3b8a85 Merge pull request #1630 from abjerner/patch-14
Typo (sorta)
2017-01-16 08:11:09 +01:00
Shannon Deminick 41244485b1 Merge pull request #1684 from umbraco/temp-U4-9377
U4-9377 It is possible to create/save entities with a blank names by …
2017-01-16 17:40:12 +11:00
Shannon ede342feba Finds all other instances where a reader would be executed inside another reader's loop and fixes 2017-01-16 16:38:23 +11:00
jamiepollock a15bdba5d7 View fix for icons not appearing in the installed package list 2017-01-15 18:26:20 -05:00
Claus 7d4a9998ca U4-9377 It is possible to create/save entities with a blank names by using the services directly
ensuring that when trying to save an entity with an empty name, directly through a service, an exception will be thrown.
2017-01-13 11:45:49 +01:00
Shannon bde439d102 fixes tests 2017-01-13 12:34:58 +11:00
Shannon ba501dbbb8 U4-9371 Examine indexes inherited unpublished nodes on index rebuild part 2 2017-01-13 10:43:34 +11:00
Mads Rasmussen 0a53f7636c remove orange color from discard changes button 2017-01-10 15:01:45 +01:00
Mads Rasmussen 6e6d552441 Merge pull request #1662 from marcemarc/patch-2
Add some green to 'stay' option - U4-9323
2017-01-10 14:59:08 +01:00
Niels Hartvig f75e0cf4ba Updates to PR based on Review 2017-01-10 12:47:00 +01:00
Sebastiaan Janssen 0dc3bff01a Merge branch 'dev-v7' into master-v7 2017-01-10 07:31:08 +01:00
Sebastiaan Janssen 358dd7f32b Move ImageProcessor.Web.Config to not be in the .Core NuGet, it's not a dll 2017-01-10 07:30:35 +01:00
Shannon 6531d027fc fixes error with server side validation + data tyep editor 2017-01-10 11:55:36 +11:00
Shannon d11e787624 updates Nuspec with IP version 2017-01-10 11:37:31 +11:00
Shannon 055ae0d126 updates latest IP mem leak fixes 2017-01-10 11:36:35 +11:00
Sebastiaan Janssen 77969960d9 Merge branch 'dev-v7' into master-v7
# Conflicts:
#	build/UmbracoVersion.txt
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Configuration/UmbracoVersion.cs
#	src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
2017-01-09 16:26:07 +01:00
Sebastiaan Janssen 52b1a08912 Merge pull request #1671 from umbraco/temp-U4-9337
Perf improvements especially around URLs and node path
2017-01-09 16:12:26 +01:00
Niels Hartvig 1a9f336a59 Add support for GUIDs in media and content pickers 2017-01-09 14:45:40 +01:00
Stephan 9ba08f275e Merge branch 'temp-U4-9337' of https://github.com/umbraco/Umbraco-CMS into temp-U4-9337 2017-01-06 09:15:43 +01:00
Stephan aadd0c9129 U4-9337 - asymmetric route caching 2017-01-06 09:12:25 +01:00
Shannon b3b79a792f Merge branch 'refs/heads/dev-v7' into temp-U4-9337 2017-01-06 17:40:40 +11:00
Shannon 59cd4b5c5c Fixes more allocations at the config level 2017-01-06 17:40:03 +11:00
Shannon f5f9ff29ce Discovered we are not passing the ISqlSyntaxProvider correctly to where so it defaults to the global one which causes some issues for libraries such as UmbracoIdentity 2017-01-06 14:32:56 +11:00
Sebastiaan Janssen a10b59d03b Revert "Fixes: U4-9217 - Sanitize tags before storing them in the database"
This reverts commit 47c8e6854e.
2017-01-05 17:14:14 +01:00
Sebastiaan Janssen e6b1ecb851 Better way of cleaning the file extension to prevent XSS attacks 2017-01-05 11:52:06 +01:00
Sebastiaan Janssen 69d6609a3b Merge pull request #1675 from umbraco/temp-U4-9339
Trim the file name so that it doesn't get accepted by the server if i…
2017-01-05 11:34:23 +01:00
Claus 2196ab3713 changing this to use ToSafeFileName instead of just trimming for blank characters.
issuing a request with any character not allowed in a file path, appended to the filename - allows you to trick the validation like with the blank characters appended.
2017-01-05 11:10:21 +01:00
Stephan 89be1cf5d2 U4-9337 - with missing file... 2017-01-05 10:50:03 +01:00
Stephan 2041e85b9e U4-9337 - add doc and test 2017-01-05 10:46:40 +01:00
Claus 6c2f0e404f Merge pull request #1672 from umbraco/temp-U4-9336
U4-9336 Deleting a node with an incorrect path of -1 will unpublish every node in the entire site
2017-01-05 10:17:50 +01:00
Stephan d5e66252dc U4-9337 - minor simplification 2017-01-05 09:25:30 +01:00
Shannon c2359a96f8 missed nuspec update 2017-01-05 17:09:37 +11:00
Shannon 1df65bc3ce Updates to latest ImageProcessor.Web to fix mime types when stored outside web root 2017-01-05 16:00:45 +11:00
Shannon 530a80d7ea Adds a check to validate the NodeDto when persisting the item to validate that it's path is correct this should prevent any data corruption in the future - if the data corruption actually came from Core 2017-01-05 10:43:33 +11:00
Shannon 8be0f68390 Adds logic to validate an entity's path when it is used to GetDescendants, adds logic to fix an entity's path when it's state is going to be persisted when GetDescendants is used (i.e. MoveToRecycleBin), added this logic to Media too, adds unit tests to support 2017-01-05 10:29:03 +11:00
Sebastiaan Janssen 05cb30d79c Trim the file name so that it doesn't get accepted by the server if it's a disallowed file 2017-01-04 17:01:26 +01:00
Sebastiaan Janssen c472d4975e Add some extra logging to ScheduledPublishing to more easily see where errors are coming from 2017-01-04 16:31:55 +01:00
Stephan 0e31fcf3db U4-9337 - fix tests 2017-01-04 15:36:29 +01:00
Stephan 13ed3303f5 U4-9337 - url routing perfs 2017-01-04 13:56:41 +01:00
Claus f200ab423d updated test to fail and updated notes. 2017-01-04 11:59:07 +01:00
Shannon d889d3e385 adds test 2017-01-04 20:00:30 +11:00
Sebastiaan Janssen 0bf27fa319 Merge pull request #1669 from umbraco/temp-U4-9335
U4-9335 UmbracoExamine needs to ignore the useTempStorage option if a…
2017-01-04 09:42:01 +01:00
Shannon 466a37aab8 fix build 2017-01-04 18:42:11 +11:00
Shannon 4ab2130d7d stop allocating and re-processing config elements every time they are accessed! 2017-01-04 18:40:00 +11:00
Shannon 256ceef875 changes PublishedContentCache to explicitly cast to XmlElement (perf) 2017-01-04 18:12:28 +11:00
Shannon 2acde54880 Adds new benchmark and updates the benchmark bootstrapper to be the supported way 2017-01-04 18:11:21 +11:00
Shannon a92d255e4b adds notes, obsoletes a few classes that shouldn't have really been there 2017-01-04 17:10:48 +11:00
Sebastiaan Janssen dc62272e6e Revert "Add Microsoft.IO.RecyclableMemoryStream to the nuspec, update to the latest version (1.2.1)"
Keep the version the same as what ImageProcessor relies on to avoid confusion and conflicts

This reverts commit 56d2230251.

# Conflicts:
#	build/NuSpecs/UmbracoCms.Core.nuspec
2017-01-03 17:04:38 +01:00
Niels Hartvig 60f6a60ca9 Update install post url to https 2017-01-03 16:11:32 +01:00
Sebastiaan Janssen eef8d21112 Merge pull request #1667 from umbraco/U-9333
Makes EXIF data in resized images default
2017-01-03 15:59:43 +01:00
Sebastiaan Janssen 6a374cbfa6 Make the NuGet install and upgrade work 2017-01-03 15:57:21 +01:00
Sebastiaan Janssen ccb8ac4cfc Merge branch 'dev-v7' into U-9333
# Conflicts:
#	src/Umbraco.Web.UI/packages.config
2017-01-03 14:19:43 +01:00
hartvig 5de21734c0 Merge pull request #1650 from Bitmapped/U4-9298
Fix U4-9298
2017-01-03 12:56:46 +01:00
Sebastiaan Janssen 18deb8ad99 Merge pull request #1666 from defsteph/patch-1
Fixes issue U4-9332
2017-01-03 12:54:09 +01:00
Claus 3426ae3722 Merge pull request #1668 from umbraco/temp-U4-9318
U4-9318 Examine indexes inherited unpublished nodes on index rebuild
2017-01-03 11:44:33 +01:00
Sebastiaan Janssen d04510f5f2 Merge pull request #1661 from umbraco/temp-U4-9319
U4-9319 Update to latest ImageProcessor v2.5.0 and ImageProcessor.Web…
2017-01-03 10:21:26 +01:00
Sebastiaan Janssen 84c0682f16 Not necessary to install Microsoft.IO.RecyclableMemoryStream, the ImageProcessor dependency will do that for us 2017-01-03 10:20:22 +01:00
Shannon 53bbc5f922 U4-9335 UmbracoExamine needs to ignore the useTempStorage option if an IDirectoryFactory config option is supplied by the indexer 2017-01-03 19:39:34 +11:00
Sebastiaan Janssen 56d2230251 Add Microsoft.IO.RecyclableMemoryStream to the nuspec, update to the latest version (1.2.1) 2017-01-03 09:25:10 +01:00
Sebastiaan Janssen 62f21d3cee Update nuspec to install latest version of ImageProcessor 2017-01-03 09:17:05 +01:00
Shannon b9c90af8be U4-9329 ExamineIndex.config should not ship with the {machinename} token for Examine
Removes Sync from being the default for developers locally (that shouldnt have been committed), actually removes the machinename token from release
2017-01-03 17:37:15 +11:00
Shannon 7a661c1b48 clears the tracked paths collection just to possible make GC a bit happier 2017-01-03 17:16:07 +11:00
Shannon c871a711dd U4-9318 Examine indexes inherited unpublished nodes on index RebuildIndex
This reduces allocations since we don't need to create a new serializer for every doc
2017-01-03 16:43:54 +11:00
Shannon d671caf3ab moves static query to lazily initialize 2017-01-03 14:36:06 +11:00
Shannon d412d90b17 fixing more IP assembly/nuget refs 2017-01-03 11:14:34 +11:00
Shannon 2f74181262 fixing more IP assembly/nuget refs 2017-01-03 11:06:15 +11:00
Shannon 45fd23ff02 Merge branch 'refs/heads/dev-v7' into temp-U4-9319 2017-01-03 10:36:45 +11:00
Shannon 4e2fb8e5dc Fixes IP nuget references and fixes up several other Nuget assembly references. 2017-01-03 10:36:26 +11:00
Niels Hartvig afbceca184 Makes EXIF data in resized images default 2017-01-02 17:29:37 +01:00
Stephan Lonntorp 1b8c68af02 Fixes issue U4-9332
U4-9332 Language of DefaultCultureDictionary is cached without the current Culture identifier
2017-01-02 14:53:57 +01:00
Shannon 98430fd573 U4-9326 Removing a master template from a Template does not update the Template's path correctly 2016-12-30 11:42:02 +11:00
Marc Goodson 4a547ba1c9 Add some green to 'stay' option - U4-9323
Closes U4-9323 http://issues.umbraco.org/issue/U4-9323
2016-12-28 15:11:10 +00:00
Shannon 59f39fd535 Ensures we remove the new DLL with the install script like other files 2016-12-28 19:48:35 +11:00
Shannon b4ca04596c U4-9319 Update to latest ImageProcessor v2.5.0 and ImageProcessor.Web v4.7.0 2016-12-28 19:21:00 +11:00
Mikkel Holck Madsen 2d94ac7a19 Merge pull request #1658 from umbraco/temp-U4-9199
Fixes: U4-9199 Grid RTE (rich text editor/ tinyMCE) does not have a m…
2016-12-27 14:45:42 +01:00
Mads Rasmussen 4c97c689e9 Fixes: U4-9199 Grid RTE (rich text editor/ tinyMCE) does not have a max height, causing the user to have to scroll up to see RTE menu buttons 2016-12-22 14:06:23 +01:00
Sebastiaan Janssen f2f4c23c80 Merge pull request #1652 from umbraco/U4-9301
Updates login controller to not accept non local urls
2016-12-20 13:55:31 +01:00
Claus 67ea15706e Merge pull request #1651 from umbraco/temp-U4-9282
U4-9282 Rollback not working
2016-12-20 11:24:05 +01:00
Claus 5cc75b8cc3 Merge pull request #1654 from umbraco/temp-U4-9302
U4-9302 Add logging of non security related connection string details…
2016-12-20 11:01:50 +01:00
Claus 653ac66ff3 Merge pull request #1653 from umbraco/temp-U4-9262
U4-9262 CleanForXss breaks rgba() values set as grid background color…
2016-12-20 10:32:36 +01:00
Shannon 3f7e593bc2 U4-9302 Add logging of non security related connection string details when the connection fails 2016-12-20 14:10:06 +11:00
Shannon 39505cd868 U4-9262 CleanForXss breaks rgba() values set as grid background color style 2016-12-20 13:27:53 +11:00
Shannon 0daaa3e785 Revert "Merge pull request #1618 from AliSheikhTaheri/U4-9180"
This reverts commit 4050a375bc, reversing
changes made to a058f26462.
2016-12-20 12:28:45 +11:00
Shannon Deminick 4050a375bc Merge pull request #1618 from AliSheikhTaheri/U4-9180
U4-9180 Optimise Log4net
2016-12-20 11:08:11 +11:00
Niels Hartvig da27108d0d Updates login controller to not accept non local urls 2016-12-19 10:44:22 +01:00
hartvig a058f26462 Update README.md 2016-12-19 09:20:21 +01:00
Shannon c8f0f05570 U4-9246 Should ship with numRecompilesBeforeAppRestart="50" to avoid max recompilations reached errors 2016-12-19 17:10:29 +11:00
Shannon 347ed2896a U4-9282 Rollback not working 2016-12-19 16:58:24 +11:00
Shannon 5c40a1f9e9 bumps version 2016-12-19 16:42:05 +11:00
Brian Powell afa39866ad Fix U4-9298 2016-12-17 21:12:45 -05:00
Shannon 7254db40eb publicizes ExamineExtensions.ConvertSearchResultToPublishedContent 2016-12-15 17:44:32 +11:00
Shannon 04481c33e7 Merge branch 'dev-v7' of https://github.com/umbraco/Umbraco-CMS into dev-v7 2016-12-15 10:45:57 +11:00
Shannon 4c107470c3 updates some angular docs 2016-12-15 10:43:53 +11:00
Warren Buckley 4b39f490f3 Merge pull request #1602 from cheeseytoastie/U4-9148_-_Splash_screen_apostophe_typo
U4-9148 noNodes Splash screen apostrophe char typo fixed (in "Be a pa…
2016-12-09 13:32:53 +00:00
Sebastiaan Janssen c607ceda48 Merge pull request #1557 from tompipe/U4-9127-SortMemberProperties
Fixed issue with member properties sort order
2016-12-09 08:59:52 +01:00
Claus d4acd4629a Merge pull request #1646 from umbraco/temp-U4-9259
Lazily creates the static queries instead of eagerly, this fixes an i…
2016-12-08 10:09:32 +01:00
Shannon b413b7b5af Lazily creates the static queries instead of eagerly, this fixes an issue of if these services are accessed before Umbraco has booted 2016-12-08 12:48:44 +11:00
Sebastiaan Janssen 5bc66c5b7f Bumps the version 2016-12-07 14:18:27 +01:00
Stephan 0415e910dc Merge pull request #1645 from umbraco/temp-U4-9255
U4-9255 Not having access to settings section causes backoffice user …
2016-12-07 13:06:24 +01:00
Claus 960040e51e U4-9255 Not having access to settings section causes backoffice user to be logged out when browsing media list views 2016-12-07 12:57:29 +01:00
Chris Houston 90136c524c Small English grammar error. 2016-12-06 11:05:59 +00:00
Sebastiaan Janssen f48ba93a01 Merge pull request #1639 from umbraco/temp-htmlencode-tags
Ensures tag values are Html encoded both on the client side and on th…
2016-12-05 08:10:41 +01:00
Shannon 4823235bf5 Ensures the cleaned value isn't double html encoded 2016-12-05 17:21:24 +11:00
Shannon 90f30c3f3c Ensures tag values are Html encoded both on the client side and on the server side to prevent any xss 2016-12-05 17:13:07 +11:00
Stephan 22397ded9b Be more tolerant with Xml cache document 2016-12-02 11:53:37 +01:00
Stephan de1da4b060 Merge pull request #1635 from umbraco/temp-startuphandler-ordering
This allows for sorting startup handlers and modifying the list of startup handlers before they execute
2016-12-02 11:48:57 +01:00
Stephan b7240f2048 Merge 2016-12-02 08:47:36 +01:00
Stephan 13f7f96101 Refactor app event resolver weights 2016-12-02 08:45:36 +01:00
Shannon 006ee00527 Ensures that all weight's for startup handlers that are in a Core namespace have a -100 default applied, all public ones by default will have 100 applied. Allows for developers to use the reserved negative numbers if they must - but we need to doc this! 2016-12-02 13:05:45 +11:00
Stephan 3e8f85a4b4 Be more tolerant with Xml cache document 2016-12-01 14:30:50 +01:00
Enkel Media 8d7f496f7c Merge pull request #1 from enkelmedia/enkelmedia-patch-1
Fixed simple typo for modelsbuilder
2016-11-30 11:18:25 +01:00
Enkel Media f62a9ed199 Fixed simple typo for modelsbuilder 2016-11-30 11:17:21 +01:00
Stephan 8305a1acd3 Merge pull request #1636 from umbraco/temp-U4-9222
U4-9222 The in memory XML cache is cloned everytime there is a content save even if there is nothing published
2016-11-28 13:06:11 +01:00
Stephan 309081cccf Merge branch origin/dev-v7 into temp-U4-9222 2016-11-28 13:06:06 +01:00
Shannon 1221c0e416 Merge branch 'dev-v7' of https://github.com/umbraco/Umbraco-CMS into dev-v7 2016-11-28 12:04:06 +01:00
Shannon c505cc689b Obsoletes access to ApplicationContext from UmbracoContext 2016-11-28 12:03:46 +01:00
Stephan 86cc3f69aa Merge pull request #1637 from umbraco/temp-sqlhelper-usings
Refactor all usages of SqlHelper, ExecuteReader and ExecuteXmlReader …
2016-11-28 10:05:48 +01:00
Stephan 3a216c055e U4-9222 - use HasPublishedVersion test 2016-11-28 09:52:45 +01:00
Sebastiaan Janssen ee3a4614c3 Refactor all usages of SqlHelper, ExecuteReader and ExecuteXmlReader into using blocks 2016-11-26 18:02:57 +01:00
Stephan bb9bc73daa Merge remote-tracking branch 'origin/dev-v7' into temp-startuphandler-ordering 2016-11-25 18:53:58 +01:00
Shannon 5b5e1847eb Updates legacy CMSNode and Document to use the PetaPoco Database intead of SqlHelper for the methods that the xml cache uses to update itself (so it's using one connection) 2016-11-25 18:08:18 +01:00
Shannon eeb94e1668 U4-9222 The in memory XML cache is cloned everytime there is a content save even if there is nothing published 2016-11-25 18:07:27 +01:00
Shannon 14bf4bbd32 throws exception if the delegate is already set 2016-11-25 15:55:33 +01:00
Shannon 0e61d18b9e Ensure core handlers are always first no matter what 2016-11-25 15:53:08 +01:00
Shannon 531fa9f25a Ensures the delegate cannot be set once the event handlers are resolved. 2016-11-25 14:34:04 +01:00
Shannon a44a670f50 Removed the IApplicationEventsFilter interface, adds a delegate property that can be set instead 2016-11-25 14:14:43 +01:00
Shannon 81750c41bd Removes events for filtering and instead use a special interface to perform the filtering, updates ctor for WeightAttribute to only accept positive numbers for public, internally it could be negative numbers, updates the filtering to always ensure that core handlers are at the beginning 2016-11-25 10:35:36 +01:00
Stephan 672568d1f5 Add missing file 2016-11-24 18:16:14 +01:00
Stephan 0179540023 Rename WeightAttribute, change default to 100, cleanup 2016-11-24 18:13:05 +01:00
Shannon ed37b84b3b makes public the WeightedPluginAttribute, uses this for resolving the startup handlers so they could be sorted, adds special event to allow modifying the startup handlers. 2016-11-24 11:20:01 +01:00
Sebastiaan Janssen 47c8e6854e Fixes: U4-9217 - Sanitize tags before storing them in the database 2016-11-23 14:24:26 +01:00
Stephan 1def61432e U4-9216 - fix saving containers 2016-11-23 10:58:52 +01:00
Shannon e9b2175506 Fixes null check problem in ExamineEvents.ContentTypeCacheRefresherCacheUpdated 2016-11-22 15:16:07 +01:00
Sebastiaan Janssen a2a4ad3947 Fixes U4-9212 2016-11-22 14:31:29 +01:00
Sebastiaan Janssen d95f818e43 Fixes U4-9212 2016-11-22 14:04:31 +01:00
Sebastiaan Janssen 64978dbe89 Merge pull request #1560 from mikkelhm/dev-v7
U4-9209 - Updates web.config.install.xdt to re-add the assemblyBinding for Html…
2016-11-21 22:54:49 +01:00
Anders Bjerner bc8b8a69b8 Typo (sorta) 2016-11-21 10:26:08 +01:00
Sebastiaan Janssen 77e9643a09 Update NuGet CDF dependency 2016-11-21 08:17:18 +01:00
Shannon 17316363f1 Merge branch 'dev-v7' of https://github.com/umbraco/Umbraco-CMS into dev-v7 2016-11-18 14:34:50 +01:00
Shannon 0e57d558f6 updates cdf version 2016-11-18 14:34:31 +01:00
Claus f23247cd85 Merge pull request #1627 from umbraco/temp-U4-9185
U4-9185 lastLockoutDate does not get set when too many invalid passwo…
2016-11-18 14:13:29 +01:00
Shannon 93ee850bcc publicize 2 events on UmbracoModule 2016-11-18 13:48:18 +01:00
Shannon 8ba6cb3abf Revert "Backport SafeCallContext, DefaultDatabaseFactory fixes from 7.6"
This reverts commit 59ace3d881.
2016-11-18 13:38:49 +01:00
Sebastiaan Janssen a266a22daf Merge pull request #1629 from dampee/patch-1
readme.md : remove spaces behind ##
2016-11-18 10:57:24 +01:00
Damiaan c4d36f06f8 remove spaces behind ##
the github markdown will show ## at the end of the line if it is followed by a space
2016-11-18 10:47:32 +01:00
Shannon Deminick 78a2a4a55b Merge pull request #1624 from umbraco/temp-u4-9195
U4-9195 - fix PetaPoco page of int
2016-11-17 17:06:02 +01:00
Shannon a70f37a53e U4-9185 lastLockoutDate does not get set when too many invalid password attempts are made 2016-11-17 16:46:06 +01:00
Mark320 9de3acfae2 U4-9192 Add jQuery migrate for user controls used in tabs that will improve backwards compatibility 2016-11-17 14:24:46 +00:00
Sebastiaan Janssen 0a883ae109 Make sure Umbraco runs in Visual Studio 2017 2016-11-16 18:55:12 +01:00
Stephan 568a4987e3 U4-9195 - fix PetaPoco page of int 2016-11-16 17:51:00 +01:00
Warren Buckley 02bfe3ad72 Merge pull request #1603 from rjabreu/U4-9036
added finish button so page only reloads after user input. also added…
2016-11-16 16:14:53 +00:00
Shannon Deminick 3eba4f462f Merge pull request #1619 from umbraco/temp-u4-6994
U4-6994 - N+1 queries
2016-11-16 13:46:22 +01:00
Shannon Deminick 55f3d72cc8 Merge pull request #1543 from umbraco/temp-u4-9108
U4-9018 - provide a way to disable server master election
2016-11-16 13:10:16 +01:00
Sebastiaan Janssen 2fa2cc1574 Merge pull request #1622 from umbraco/temp-U4-9188
U4-9188 Remove unnecessary duplicate index IX_cmsPropertyData
2016-11-16 10:18:21 +01:00
Shannon 95f5773046 U4-9188 Remove unnecessary duplicate index IX_cmsPropertyData 2016-11-16 08:23:53 +01:00
Stephan 289d7a01d8 Fix BulkInsert, was rolling back 2016-11-15 18:41:03 +01:00
Sebastiaan Janssen 47dbb648fb Merge pull request #1614 from umbraco/temp-u4-9077
U4-9077 - relation type repository cache
2016-11-15 18:30:01 +01:00
Stephan e499f2d148 U4-9108 - add missing change 2016-11-15 13:44:44 +01:00
Stephan 62325f0a27 Merge branch origin/dev-v7 into temp-u4-9108 2016-11-15 13:34:13 +01:00
Shannon Deminick ff499bff04 Merge pull request #1607 from umbraco/temp-u4-backport
U4-8909 backport from 7.6
2016-11-15 13:11:05 +01:00
Sebastiaan Janssen 26a04ca7e9 Merge pull request #1610 from umbraco/temp-U4-9134
U4-9134 XSS security issue in the grid
2016-11-15 12:56:21 +01:00
Claus 6f39439e3d removing string.empty checks for attributes.
removing the redundant clean for outputting model.value in TextString.cshtml.
2016-11-15 12:10:36 +01:00
Claus 34d4b284fe Merge pull request #1613 from umbraco/temp-U4-7833
U4-7833 Changing doc type results in invalid cache, lucene indexes, e…
2016-11-15 11:14:56 +01:00
Stephan a5695b71eb Merge pull request #1553 from umbraco/temp-U4-9105
U4-9105 SPIKE: Ensure our Lambda expression tree parsers at our service layer are not performance bottlenecks
2016-11-14 18:58:16 +01:00
Stephan 539e7c80bf Merge branch origin/dev-v7 into temp-U4-9105 2016-11-14 18:45:56 +01:00
Stephan cfc1704c64 Fix build after merge 2016-11-14 18:33:16 +01:00
Stephan bbe1cb1813 Merge branch origin/dev-v7 into temp-u4-6994 2016-11-14 18:31:36 +01:00
Stephan feb120cbeb Merge branch 'dev-v7' into temp-u4-6994 2016-11-14 18:20:06 +01:00
Stephan 2bc07ad4cc Merge branch 'dev-v7' into temp-U4-9105 2016-11-14 18:13:45 +01:00
Sebastiaan Janssen 1c036b1a11 Merge pull request #1562 from umbraco/temp-u4-9136
Temp u4 9136
2016-11-14 18:04:37 +01:00
Stephan f5fe07ffb7 U4-6994 - more N+1 and refactoring 2016-11-14 17:52:40 +01:00
Shannon 213f02cbc6 Revert "U4-7833 Changing doc type results in invalid cache, lucene indexes, etc..."
This reverts commit 3deda7efab.
2016-11-14 17:39:46 +01:00
Shannon 2de465e8f3 Ensures that indexes are rebuild for items that have had their content type's or property type's aliases changed, also ensures that the content refresher kicks in if a property type alias has changed (this wasn't previously being done) 2016-11-14 17:39:28 +01:00
Sebastiaan Janssen 5b714572e3 Bump version 2016-11-14 17:04:55 +01:00
Sebastiaan Janssen 8e95acb4c5 Merge pull request #1561 from umbraco/temp-U4-9132
U4-9132 Macros from packages are empty when restored
2016-11-14 17:02:07 +01:00
Sebastiaan Janssen 9c40218f48 Merge pull request #1551 from umbraco/temp-U4-9104
U4-9104 Update the UmbracoExamine logic for Media to read the data di…
2016-11-14 15:41:31 +01:00
Sebastiaan Janssen 714b7019c5 Fix unit tests to have the correct types as well 2016-11-14 15:38:50 +01:00
Sebastiaan Janssen 935d010dd2 The foreach is actually more readable 2016-11-14 15:26:25 +01:00
Sebastiaan Janssen 199c502396 Get the correct types 2016-11-14 15:24:41 +01:00
Ali Sheikh Taheri 19b24b5109 Added maximum number of backups to keep. 2016-11-14 11:56:21 +00:00
Ali Sheikh Taheri 3aaa33ebe9 added .log file extension for backup logs. 2016-11-14 11:55:09 +00:00
Ali Sheikh Taheri 4ae7a32e29 Fix for maximum file size. 2016-11-14 11:54:42 +00:00
Claus f2c406f168 Merge pull request #1572 from umbraco/temp-U4-4196-gkonings-html5-dropzone
U4-4196 U7 - Umbraco HTML5 Drop Zone should honor media types
2016-11-14 09:59:22 +01:00
Claus 2115367353 updating the migration to include only needed ids.
formatting stuff in js.
2016-11-14 09:57:17 +01:00
Alexander Bryukhov 87814fa43c Update UI lang file for ru-ru
All latest 7.5.5 changes
2016-11-12 12:12:41 +07:00
Claus 9e24b694b3 adding migration to retain functionality as it has always been in root when upgrading. 2016-11-10 13:09:22 +01:00
Claus 4f5bc9eb89 updating MediaController with AutoSelect option. 2016-11-10 12:36:52 +01:00
Mads Rasmussen f9d8754d88 add support for one custom media type 2016-11-10 12:00:07 +01:00
Mads Rasmussen 07c2a14321 If there is only 1 allowed media type set the alias to auto to let the server decide 2016-11-10 11:13:00 +01:00
Mads Rasmussen e77bddd0f2 add filtering of media types 2016-11-10 11:08:28 +01:00
Stephan 42522f5af3 U4-6994 - remove N+1 in ContentRepository.GetByPublishedVersion, .GetAllVersions 2016-11-09 17:21:14 +01:00
Stephan 5c3d605d34 U4-9077 - in addition, deal with N+1 for relations 2016-11-09 15:33:28 +01:00
Claus 3deda7efab U4-7833 Changing doc type results in invalid cache, lucene indexes, etc...
For now: add messages in the UI to tell people they need to publish the items again and rebuilt their indexes.
2016-11-09 13:09:31 +01:00
Stephan b441c73604 U4-9077 - relation type cache refresher + policy 2016-11-09 13:05:23 +01:00
Claus df59596775 Merge branch 'crgrieve-U4-4410' into dev-v7 2016-11-09 11:49:20 +01:00
Claus cdb0c57416 readding the fix crgrieve did for treepicker.controller.js. 2016-11-09 11:48:32 +01:00
Claus 0749c6865d Merge branch 'U4-4410' of git://github.com/crgrieve/Umbraco-CMS into crgrieve-U4-4410 2016-11-09 11:40:25 +01:00
Stephan a80bb1e219 Merge branch 'U4-9077' of https://github.com/lars-erik/Umbraco-CMS into temp-u4-9077 2016-11-09 11:36:42 +01:00
Claus ff2805c428 mostly just formatting and cleanup - no changes as such. 2016-11-09 11:13:35 +01:00
Claus 69c45a4b05 Merge pull request #1612 from tnielsenskybruddk/dev-v7
Link to member type from member properties
2016-11-09 10:29:49 +01:00
Claus 737244a65e Merge pull request #1567 from tnielsenskybruddk/dev-v7
Link to mediatype from media properties
2016-11-09 10:15:14 +01:00
Claus 7018969d14 removing unused language keys. 2016-11-09 09:29:43 +01:00
Claus d1ba7f31f8 Merge pull request #1566 from rpjengaard/dev-v7
Removed old options for linked targets and replaced with checkbox ins…
2016-11-09 09:18:27 +01:00
Thomas Nielsen cbfd04e950 Link to member type from member properties 2016-11-08 23:37:29 +01:00
Thomas Nielsen 981c0b83d3 Oops wrong pull request 2016-11-08 23:16:10 +01:00
Thomas Nielsen 16ae5cf634 Link to member type from member properties 2016-11-08 23:00:14 +01:00
Claus 4a42bfa9bb U4-9160 Headline in the "Republish entire site" dialog is not translated 2016-11-08 10:53:29 +01:00
Claus 3e32652b24 Merge pull request #1565 from abjerner/patch-13
Headline in the "Republish entire site" dialog is now translated
2016-11-08 10:52:42 +01:00
Claus 957096a70a fixing links to be https. 2016-11-08 10:33:05 +01:00
Claus 8bb069e996 U4-9134 XSS security issue in the grid
exposing xss clean method on templateutilities.
making the clean xss string extensions public instead of internal.
ensuring the included grid renderers clean for xss.
ensuring the included grid editors using html.raw with value directly, cleans for xss.
2016-11-08 09:55:24 +01:00
Claus a0c672eb91 Merge pull request #1568 from rasmusfjord/dev-v7
Translated dialogs to DK/EN
2016-11-08 08:44:01 +01:00
Claus eddd996ed6 Merge pull request #1601 from piotrwegner/dev-v7-pl-translation
added PL translation
2016-11-08 08:38:34 +01:00
crgrieve c0a2326a8d Removing whitespace when parsing MNTP comma separated list. 2016-11-07 21:03:57 +00:00
crgrieve e429f8611e Remove all whitespace when parsing MNTP allowed doctypes. 2016-11-07 20:53:59 +00:00
Claus fcca95787b fixing backwards compatibility. 2016-11-07 13:05:17 +01:00
Ivar 8944870d4f Renamed icon-globe---europe-africa to icon-globe-europe-africa 2016-11-07 13:05:15 +01:00
Claus b93c3d9caa Merge pull request #1564 from abryukhov/patch-6
Update ru.xml UI language file
2016-11-07 11:56:44 +01:00
Claus 4d2aba2577 Merge pull request #1599 from AndyFelton/U4-9147
Modified grammar on health check dashboard
2016-11-07 11:26:02 +01:00
Claus ce780ee4f4 Merge pull request #1595 from AndyFelton/U4-9146
Health check data integrity colons missing
2016-11-07 11:24:25 +01:00
Claus a6134bfacd Merge pull request #1569 from AronGreen/dev-v7
Danish translations related to Redirect URL Management
2016-11-07 11:20:43 +01:00
Claus a96f48d0a8 changing media controller logic. 2016-11-04 13:52:36 +01:00
ricardo abreu 7085f198a9 changed reloadPage function ($window to window) 2016-11-03 17:29:46 +00:00
ricardo abreu 4f211aaf48 added finish button so page only reloads after user input. also added localization for en/en_us 2016-11-03 17:13:27 +00:00
Piotr Wegner 4d2bf19891 added PL translation 2016-11-03 17:09:01 +00:00
Steve Morgan dfbadb1550 U4-9148 noNodes Splash screen apostrophe char typo fixed (in "Be a part of the community" - we're) 2016-11-03 17:03:33 +00:00
Sebastiaan Janssen 0eeb481d85 Merge pull request #1586 from LottePitcher/U4-9068
Truncate should not finish halfway through a two word unicode character
2016-11-03 17:53:49 +01:00
Andy Felton 66bce71a8b "it's" replaced with "its" on "Checks to see if your site is revealing information in it's headers that gives away unnecessary details about the technology used to build and host it." 2016-11-03 16:51:33 +00:00
Andy Felton b198ecbc02 Modifed grammer as follows
MacroErrors are set to 'throw' which will prevent some or all pages in your site from loading completely when there's any errors in macros. Rectifying this will set the value to 'inline'. modifed to  "when there's any errors in macros" should be replaced with "if there are any errors in macros"
2016-11-03 16:51:06 +00:00
Sebastiaan Janssen ecbe058d00 Merge pull request #1591 from RachBreeze/U4-7239
U4-7239 Now trims the parameters
2016-11-03 17:16:22 +01:00
Andy Felton 22f37d02dc Added missing colon 2016-11-03 16:00:04 +00:00
Lotte Pitcher c8b23195f6 When truncating if the last characters was the first half of a two word unicode character, also include the second half to avoid YSOD 2016-11-03 14:30:38 +00:00
Sebastiaan Janssen 02840965d7 Merge pull request #1577 from Draga/U4-9055
U4-9055
2016-11-03 15:25:03 +01:00
Sebastiaan Janssen 5b13ab255a Merge pull request #1575 from readingdancer/U4-9091
U4-9091 - Fixed the font being used in the Media Picker for files
2016-11-03 15:09:22 +01:00
CORP\Rachel.Breeze ae659e3c84 U4-7239 Now trims the parameters so that if a default is set in a comma and space seperated list, the parameter is now populated. 2016-11-03 13:40:46 +00:00
Stefano Chiodino c1ffefc6af U4-9055 remove background-color on preview that was interfering with the website
style
2016-11-03 11:51:01 +00:00
Chris Houston 1b2d75ee52 Fixed the font being used in the Media Picker when a file has been selected. 2016-11-03 11:43:03 +00:00
Sebastiaan Janssen d5b2ce9c09 Merge pull request #1571 from drpeck/U4-8183
(U4-8183) Changed the link picker dialog from Page Title to Link title
2016-11-03 12:25:27 +01:00
David Peck 9162575b35 (U4-8183) Changed the casing to match other labels 2016-11-03 10:53:33 +00:00
David Peck 0ff26aa442 (U4-8183) Changed the link picker dialog from Page Title to Link title in en and en_us 2016-11-03 10:50:10 +00:00
Claus a06b920785 Merge pull request #1563 from umbraco/temp-u4-9137
U4-9137 - Fix PublishedMediaCache getting trashed media
2016-11-03 11:25:21 +01:00
Claus 0441827578 using the provided media type when uploading images, instead of using default Image media type. 2016-11-03 10:41:06 +01:00
Claus 85c2ec68ac allowing default image and file media types at root. 2016-11-03 10:39:59 +01:00
Lars-Erik Aabech bf50c25854 Only caching relation types
First hackathon task done. :)
2016-11-03 10:26:31 +01:00
Mads Rasmussen 24402388b4 position the overlay to the right 2016-11-03 09:57:10 +01:00
AronGreen 7b345d6cb0 Danish translations related to Redirect URL Management 2016-11-02 22:02:47 +01:00
Rasmus Fjord 018f9f1776 removed hg ignore 2016-11-02 21:30:18 +01:00
Rasmus Fjord 71f3948de2 Translated copy dialog (relate to and include descendants) to dk 2016-11-02 21:23:02 +01:00
Thomas Nielsen ea67e21796 Link to mediatype from media properties 2016-11-02 21:20:03 +01:00
René Pjengaard Bank a83739c269 Removed old options for linked targets and replaced with checkbox instead in linkpicker 2016-11-02 20:58:21 +01:00
Rasmus Fjord cc5f127dcf Translated "openInNewWindow" to a better sound in danish. 2016-11-02 20:57:17 +01:00
Rasmus Fjord e015e73abe Translation of Moving item(media / content) 2016-11-02 20:55:15 +01:00
Rasmus Fjord 0624d224a9 Translated set permissions windows 2016-11-02 20:30:05 +01:00
Rasmus Fjord 030708194a Oversat publish events 2016-11-02 20:15:55 +01:00
Anders Bjerner a5b0fbb83d Headline in the "Republish entire site" dialog is now translated 2016-11-02 19:51:54 +01:00
Alexander Bryukhov 5a5be024e4 Update ru.xml UI language file
Dictionary item editor keys added
2016-11-03 00:12:44 +07:00
Rasmus Fjord b1b42b5262 Merge branch 'dev-v7' of https://github.com/rasmusfjord/Umbraco-CMS into dev-v7 2016-11-02 16:45:31 +01:00
Stephan 07f4da9b00 Cleanup WebProfiler from 8.x 2016-11-02 14:26:04 +01:00
Stephan 59ace3d881 Backport SafeCallContext, DefaultDatabaseFactory fixes from 7.6 2016-11-02 14:26:04 +01:00
Mads Rasmussen 0426f1f17d Changes from Gerard Konings + a couple of small fixes 2016-11-02 14:24:17 +01:00
Stephan c2d3d057a5 U4-9137 - Fix PublishedMediaCache getting trashed media 2016-11-02 13:59:04 +01:00
Stephan 5426c570fc U4-9136 - group size 200 2016-11-02 13:53:03 +01:00
Stephan 3a6e2e6735 U4-9136 - rebuild Xml data in a more resilient way 2016-11-02 13:52:58 +01:00
Claus ef38808ba5 Merge branch 'merge-U4-8782' into dev-v7 2016-11-01 14:34:56 +01:00
Claus 7fe5f0c099 Merge branch 'Flaeng-Email-validation-on-user-form' into merge-U4-8782
Conflicts:
	src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs
2016-11-01 14:28:38 +01:00
Claus 5f615d30b2 Merge branch 'Email-validation-on-user-form' of git://github.com/Flaeng/Umbraco-CMS into Flaeng-Email-validation-on-user-form 2016-11-01 13:35:04 +01:00
Claus 7ce2bbe9b1 Merge branch 'mikecp-temp-U4-8782' into merge-U4-8782 2016-11-01 13:34:19 +01:00
Claus 4c408eb1b7 Merge branch 'temp-U4-8782' of git://github.com/mikecp/Umbraco-CMS into mikecp-temp-U4-8782 2016-11-01 13:32:09 +01:00
Sebastiaan Janssen 82a2f50d03 Merge pull request #1554 from umbraco/temp-u4-9121
U4-9121 - improve url perfs
2016-11-01 09:56:11 +01:00
Claus e8237f6190 adding culture parameters for this and a few others that were missing. 2016-10-31 15:09:10 +01:00
Claus 4d13622432 U4-9132 Macros from packages are empty when restored
changing the package installer to use the new API macroservice instead of the old legacy classes.
marked a few methods as obsoleted.
2016-10-31 14:55:11 +01:00
Stephan c77174059d Backport MainDom cleanup from 7.6 2016-10-31 11:06:17 +01:00
Stephan 69b442a31a Fix - PublicAccessEntry rules clearing 2016-10-31 11:02:00 +01:00
mikkelhm e9724c8785 Updates web.config.install.xdt to re-add the assemblyBinding for HtmlAgilityPack after removing it earlier 2016-10-30 20:46:18 +01:00
Tom Pipe 0a49d54a85 Fixed issue with member properties sort order 2016-10-29 18:23:25 +01:00
Stephan 958392f09b U4-9105 - bit of cleanup 2016-10-28 16:30:20 +02:00
Stephan cb79bcd876 Merge branch 'dev-v7' into temp-U4-9105 2016-10-28 14:38:43 +02:00
Stephan c500f98ad8 U4-9121 - improve url perfs 2016-10-28 14:33:44 +02:00
Sebastiaan Janssen 41a8dd10de Merge pull request #1545 from umbraco/temp-u4-9090
U4-9090 - Upgrade to ModelsBuilder 3.0.5 (reuse compiled live dll on …
2016-10-28 10:37:13 +02:00
Sebastiaan Janssen 9283e57429 Merge branch 'dev-v7' of https://github.com/umbraco/Umbraco-CMS into dev-v7 2016-10-27 22:09:43 +02:00
Sebastiaan Janssen 3bdb3f8c1e U4-9065 Update github readme from Kris's notes
#U4-9065 Fixed
2016-10-27 22:08:52 +02:00
Sebastiaan Janssen 3aee8b069d Merge pull request #1547 from umbraco/temp-U4-9039
Fixes: U4-9039 No progress indication when uploading local package
2016-10-27 21:47:05 +02:00
Sebastiaan Janssen 3e79b65539 Merge pull request #1549 from umbraco/temp-u4-8995
U4-8995 - fix health check for trySkipIisError
2016-10-27 21:30:55 +02:00
Shannon e2d8a28087 ensures the generic base class static query instance is lazily created 2016-10-27 18:45:42 +02:00
Shannon ba0ef3676d U4-9109 changes all usage of automapper ResolveUsing<T> to instantiate the resolver explicitly - this prevents IoC frameworks registered against automapper to not explode if these resolvers have not been added to the container. 2016-10-27 18:32:16 +02:00
Shannon 3f609fdf90 Moves a few queries to re-used query instances 2016-10-27 18:03:13 +02:00
Shannon 9717d03a75 oops, didn't mean to commit this 2016-10-27 18:03:13 +02:00
Shannon 21e2c35ab0 U4-9111 Benchmark BulkCopy changes from U4-9107 2016-10-27 18:03:13 +02:00
Claus 563f8fbdca forgot one :) 2016-10-27 11:07:41 +02:00
Claus 221354a7d8 U4-9104 Update the UmbracoExamine logic for Media to read the data directly from the umbracoXml table.
updated some tests to use the testbehavior attribute so they dont rely on being run in a specific order.
2016-10-27 10:47:32 +02:00
Stephan 7495d89a79 U4-9107 - enable bench on empty db 2016-10-26 19:26:10 +02:00
Stephan 15ad07f5db Merge pull request #1544 from umbraco/temp-U4-9107
U4-9107 Change BulkInsertRecords to use BulkCopy or TableDirect (SQLCE)
2016-10-26 19:24:41 +02:00
Stephan c95c4c359a Merge branch 'dev-v7' into temp-U4-9107 2016-10-26 18:03:43 +02:00
Stephan 1a1dd99a89 Merge pull request #1550 from umbraco/temp-U4-9101
U4-9101 fix up how cache refresh instructions are read or written and to prevent duplicate processing
2016-10-26 18:01:58 +02:00
Stephan 71afe908b4 U4-9101 - comments and misc 2016-10-26 18:01:32 +02:00
Stephan e3c3acfca9 Merge branch 'dev-v7' into temp-U4-9101 2016-10-26 17:48:41 +02:00
Shannon 7e81768abc adds exception checking to ExamineExtensions 2016-10-26 14:25:32 +02:00
Stephan 6d0bca471e U4-8995 - fix health check for trySkipIisError 2016-10-26 14:24:31 +02:00
Shannon 8e4e7e5b19 Ensures no duplicates are processed between rows 2016-10-26 12:19:36 +02:00
Shannon c667627d2f Changes max row retrieval to 100 2016-10-26 12:01:43 +02:00
Shannon 8b411c3d46 Adds checks to see if the app is shutting down while processing instruction batches, if so it will exit instruction processing. Also set a timeout on the wait lock so that if the app is shutting down it will after 5 seconds. 2016-10-26 11:37:59 +02:00
Claus db7c20d99a fixing unit test failing on da-DK culture sorting. 2016-10-26 11:32:25 +02:00
Shannon f153332ad5 updates logging levels for MainDom 2016-10-25 16:56:55 +02:00
Shannon 876d78b4ad During instruction processing, always check if the app is shutting down and if so then exit, if it's in the middle of a batch it will exit even if it's processed a few and will not save the last synced id meaning the batch will get processed again .Reverts the change to process only a portion of the instructions json blob and save back to the db - that won't work in LB scenarios. 2016-10-25 16:23:49 +02:00
Shannon 68e871d175 Ensures that any given JSON blob written to the instructions can only contain the maximum number of instructions to process 2016-10-25 16:01:55 +02:00
Shannon d7d4b26a66 This changes the instruction fetch process to query for the top records based on the MaxProcessingInstructionCount. This changes the Fetch to be a Query so they are not all loaded into memory and instead uses a db reader. This checks if any JSON blob contains more than the MaxProcessingInstructionCount and if so, it breaks out of the reader, takes the max amount to be processed and re-saves the remaining back to the same record so that this single request does not over-process instructions (which can take a long time and cause all sorts of problems) 2016-10-25 15:50:33 +02:00
Mads Rasmussen f1d5ccb1e3 Fixes: U4-9039 No progress indication when uploading local package 2016-10-25 15:40:31 +02:00
Stephan bf67cd5acd Merge pull request #1539 from umbraco/temp-U4-9093
U4-9093 Fix SQL used to rebuild the media & content xml structures in…
2016-10-25 14:05:13 +02:00
Stephan e330d40005 U4-9090 - Upgrade to ModelsBuilder 3.0.5 (reuse compiled live dll on restart) 2016-10-25 12:40:27 +02:00
Shannon 1a6986cfcf U4-9111 Benchmark BulkCopy changes from U4-9107 2016-10-25 12:34:06 +02:00
Stephan b617b9e391 U4-9108 - add config setting 2016-10-25 12:32:50 +02:00
Shannon dadcbc8b31 U4-9107 Change BulkInsertRecords to use BulkCopy or TableDirect (SQLCE) 2016-10-25 12:29:17 +02:00
Stephan 07bb57fac8 U4-9018 - provide a way to disable server master election 2016-10-25 12:22:06 +02:00
Shannon 92f5ad220a oops, changes back so we have less joins, we don't need to query on cmsDocument or published, we know it will be that 2016-10-21 18:17:31 +02:00
Shannon 1a8f53d1de Fixes tests 2016-10-21 18:16:02 +02:00
Shannon 4f77caee02 U4-9093 Fix SQL used to rebuild the media & content xml structures in the data integrity check 2016-10-21 16:23:32 +02:00
Shannon d64757f3b0 U4-9093 Fix SQL used to rebuild the media & content xml structures in the data integrity check 2016-10-21 16:17:13 +02:00
Shannon 74a5bf5503 Merge branch 'dev-v7' of https://github.com/umbraco/Umbraco-CMS into dev-v7 2016-10-21 16:12:11 +02:00
Shannon ee8efbb1ab Adds notes about bulk copy 2016-10-21 16:11:57 +02:00
Claus 7ab2703388 Merge branch 'sniffdk-U4-205' into dev-v7 2016-10-20 13:59:00 +02:00
Claus f753dfa2a7 minor updates and a little reformatting. 2016-10-20 13:56:52 +02:00
Claus 8fa02913be Merge branch 'U4-205' of git://github.com/sniffdk/Umbraco-CMS into sniffdk-U4-205 2016-10-20 12:53:44 +02:00
Lars-Erik Aabech f199b0a651 Added default cachehelper to the relation repositories 2016-10-18 22:35:46 +02:00
Dennis Jørgensen fb83b9dca3 Changed email validation to the validation used when creating users (MembershipProviderBase). 2016-10-16 18:45:01 +02:00
Dennis Jørgensen ac0159f67e Changed style for form controls and text for email-field 2016-10-15 22:47:35 +02:00
Dennis Jørgensen f1c8fb7b17 Added validation to the user edit-form in the backoffice 2016-10-15 22:00:03 +02:00
Dennis Jørgensen 3b43979a06 Merge remote-tracking branch 'refs/remotes/umbraco/dev-v7' into dev-v7 2016-10-15 20:24:19 +02:00
michael f0db4824ec Solve issue U4-8782 : E-mail validation when editing user + update error message when creating new user to use localized text 2016-09-30 23:20:17 +02:00
Rasmus Fjord bd3ad153d2 bah 2016-09-26 11:05:15 +02:00
michael 2d0725fbad update with latest labels from en_us.xml 2016-09-22 12:15:13 +02:00
michael 1a0ced4f15 Merge remote-tracking branch 'umbraco/dev-v7' into dev-v7 2016-09-22 01:07:01 +02:00
Michael Latouche 6407c24977 Update fr.xml 2016-09-10 01:51:45 +02:00
michael fb8d71fc63 Merge remote-tracking branch 'umbraco/dev-v7' into dev-v7
# Conflicts:
#	src/Umbraco.Web.UI/umbraco/config/lang/fr.xml
2016-09-10 01:45:47 +02:00
michael 8ee98ebdf9 Review and add translation labels to language file fr.xml (sync with latest en version) 2016-09-09 13:37:24 +02:00
michael d74a30e4e4 Review and add translation labels to language file fr.xml 2016-09-09 13:17:53 +02:00
Arnold Visser ca1f43d048 Merge pull request #1 from ArnoldV/Dutch-Language-Update
Dutch Languagefile Update
2016-09-06 10:53:49 +02:00
Arnold Visser e962a48138 Dutch Languagefile Update
This updates the Dutch language file with new/missing translations and fixes some wrong translations used by the grid.
2016-09-06 10:53:09 +02:00
Mads Krohn 3e46105ba8 Stupid git ... 2016-02-16 11:12:04 +01:00
Mads Krohn d8eaab2aa4 added a missing <br> to the danish translation. added current user to the changeKey text. 2016-02-16 11:03:01 +01:00
Mads Krohn ce897509ba added localization 2016-02-16 11:03:01 +01:00
Mads Krohn 330cd7a409 Check if the new key already exists. Added error message. Enhanced error handling logic. 2016-02-16 11:03:01 +01:00
unknown 56492cd1b2 implemented functionality for changing the key for dictionary items 2016-02-16 11:03:01 +01:00
Mads Krohn 9427979b1c Check if the new key already exists. Added error message. Enhanced error handling logic. 2016-02-16 00:06:56 +01:00
Mads Krohn 9d47529d20 Merge branch 'U4-205' of https://github.com/sniffdk/Umbraco-CMS into U4-205 2016-02-15 22:38:55 +01:00
unknown eec651c9d3 implemented functionality for changing the key for dictionary items 2016-02-15 22:36:19 +01:00
unknown 3921f88e30 implemented functionality for changing the key for dictionary items 2016-02-09 00:50:25 +01:00
398 changed files with 18457 additions and 7864 deletions
+1
View File
@@ -141,3 +141,4 @@ build/docs.zip
build/ui-docs.zip
build/csharp-docs.zip
build/msbuild.log
.vs/
+20 -10
View File
@@ -1,8 +1,15 @@
Umbraco CMS
===========
Umbraco is a free open source Content Management System built on the ASP.NET platform.
The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 390,000 websites worldwide: [https://umbraco.com](https://umbraco.com)
[![ScreenShot](vimeo.png)](https://vimeo.com/172382998/)
## Umbraco CMS ##
Umbraco is a free open source Content Management System built on the ASP.NET platform. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
## Building Umbraco from source ##
The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `grunt vs` in `src\Umbraco.Web.UI.Client`.
If you're interested in making changes to Belle without running Visual Studio make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md).
@@ -13,30 +20,33 @@ Note that you can always [download a nightly build](http://nightly.umbraco.org/?
[![ScreenShot](http://umbraco.com/images/whatisumbraco.png)](https://umbraco.tv/videos/umbraco-v7/content-editor/basics/introduction/cms-explanation/)
## Umbraco - the simple, flexible and friendly ASP.NET CMS ##
## Umbraco - The Friendly CMS ##
**More than 350,000 sites trust Umbraco**
For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box.
For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box. It's a developer's dream and your users will love it too.
Umbraco is not only loved by developers, but is a content editors dream. Enjoy intuitive editing tools, media management, responsive views and approval workflows to send your content live.
Used by more than 350,000 active websites including [http://daviscup.com](http://daviscup.com), [http://heinz.com](http://heinz.com), [http://peugeot.com](http://peugeot.com), [http://www.hersheys.com/](http://www.hersheys.com/) and **The Official ASP.NET and IIS.NET website from Microsoft** ([http://asp.net](http://asp.net) / [http://iis.net](http://iis.net)), you can be sure that the technology is proven, stable and scales.
Used by more than 350,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scales. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 200,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay.
To view more examples, please visit [http://umbraco.com/why-umbraco/#caseStudies](http://umbraco.com/why-umbraco/#caseStudies)
To view more examples, please visit [https://umbraco.com/why-umbraco/#caseStudies](https://umbraco.com/why-umbraco/#caseStudies)
## Why Open Source? ##
As an Open Source platform, Umbraco is more than just a CMS. We are transparent with our roadmap for future versions, our incremental sprint planning notes are publicly accessible and community contributions and packages are available for all to use.
## Downloading ##
The downloadable Umbraco releases live at [http://our.umbraco.org/download](http://our.umbraco.org/download).
The downloadable Umbraco releases live at [https://our.umbraco.org/download](https://our.umbraco.org/download).
## Forums ##
We have a forum running on [http://our.umbraco.org](http://our.umbraco.org). The discussions group on [Google Groups](https://groups.google.com/forum/#!forum/umbraco-dev) is for discussions on developing the core, and not on Umbraco-implementations or extensions in general. For those topics, please use [http://our.umbraco.org](http://our.umbraco.org).
Peer-to-peer support is available 24/7 at the community forum on [https://our.umbraco.org](https://our.umbraco.org).
## Contribute to Umbraco ##
If you want to contribute back to Umbraco you should check out our [guide to contributing](http://our.umbraco.org/contribute).
Umbraco is contribution focused and community driven. If you want to contribute back to Umbraco please check out our [guide to contributing](https://our.umbraco.org/contribute).
## Found a bug? ##
Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](http://our.umbraco.org/contribute/report-an-issue-or-request-a-feature).
Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](https://our.umbraco.org/contribute/report-an-issue-or-request-a-feature).
To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org).
+7 -7
View File
@@ -27,18 +27,18 @@
<dependency id="SharpZipLib" version="[0.86.0, 1.0.0)" />
<dependency id="MySql.Data" version="[6.9.8, 7.0.0)" />
<dependency id="xmlrpcnet" version="[2.5.0, 3.0.0)" />
<dependency id="ClientDependency" version="[1.9.1, 2.0.0)" />
<dependency id="ClientDependency-Mvc5" version="[1.8.0, 2.0.0)" />
<dependency id="ClientDependency" version="[1.9.2, 2.0.0)" />
<dependency id="ClientDependency-Mvc5" version="[1.8.0.0, 2.0.0)" />
<!-- AutoMapper can not be updated due to: https://github.com/AutoMapper/AutoMapper/issues/373#issuecomment-127644405 -->
<dependency id="AutoMapper" version="[3.3.1, 4.0.0)" />
<dependency id="Newtonsoft.Json" version="[6.0.8, 10.0.0)" />
<dependency id="Examine" version="[0.1.70, 1.0.0)" />
<dependency id="ImageProcessor" version="[2.4.5, 3.0.0)" />
<dependency id="ImageProcessor.Web" version="[4.6.6, 5.0.0)" />
<dependency id="Examine" version="[0.1.81, 1.0.0)" />
<dependency id="ImageProcessor" version="[2.5.2, 3.0.0)" />
<dependency id="ImageProcessor.Web" version="[4.8.2, 5.0.0)" />
<dependency id="semver" version="[1.1.2, 2.0.0)" />
<dependency id="UrlRewritingNet" version="[2.0.7, 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.4, 2.0.0)" />
<dependency id="Markdown" version="[1.14.7, 2.0.0)" />
</dependencies>
</metadata>
<files>
@@ -103,4 +103,4 @@
<file src="..\_BuildOutput\bin\UmbracoExamine.pdb" target="lib" />
<file src="..\..\src\UmbracoExamine\**\*.cs" exclude="..\..\src\**\TemporaryGeneratedFile*.cs" target="src\UmbracoExamine" />
</files>
</package>
</package>
+4 -1
View File
@@ -17,7 +17,8 @@
<dependencies>
<dependency id="UmbracoCms.Core" version="[$version$]" />
<dependency id="Newtonsoft.Json" version="[6.0.8, 10.0.0)" />
<dependency id="Umbraco.ModelsBuilder" version="[3.0.4, 4.0.0)" />
<dependency id="Umbraco.ModelsBuilder" version="[3.0.5, 4.0.0)" />
<dependency id="ImageProcessor.Web.Config" version="[2.3.0, 3.0.0)" />
</dependencies>
</metadata>
<files>
@@ -43,6 +44,8 @@
<file src="tools\trees.config.install.xdt" target="Content\config\trees.config.install.xdt" />
<file src="tools\umbracoSettings.config.install.xdt" target="Content\config\umbracoSettings.config.install.xdt" />
<file src="tools\Views.Web.config.install.xdt" target="Views\Web.config.install.xdt" />
<file src="tools\processing.config.install.xdt" target="Content\Config\imageprocessor\processing.config.install.xdt" />
<file src="tools\cache.config.install.xdt" target="Content\Config\imageprocessor\cache.config.install.xdt" />
<file src="build\**" target="build" />
</files>
</package>
+1 -1
View File
@@ -335,7 +335,7 @@
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="HtmlAgilityPack" publicKeyToken="bd319b19eaf3b43a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.4.9.0" newVersion="1.4.9.0" />
</dependentAssembly>
@@ -0,0 +1,6 @@
<?xml version="1.0"?>
<caching xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<caches>
<cache name="DiskCache" trimCache="false" xdt:Transform="SetAttributes(trimCache)" xdt:Locator="Match(name)" />
</caches>
</caching>
+1
View File
@@ -71,6 +71,7 @@ if ($project) {
if(Test-Path $umbracoBinFolder\ImageProcessor.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\ImageProcessor.Web.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.Web.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Lucene.Net.dll) { Remove-Item $umbracoBinFolder\Lucene.Net.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll) { Remove-Item $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll -Force -Confirm:$false }
@@ -0,0 +1 @@
<processing xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform" xdt:Transform="SetAttributes(preserveExifMetaData)" preserveExifMetaData="true" />
+1 -1
View File
@@ -1,2 +1,2 @@
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
7.5.4
7.5.10
-4
View File
@@ -49,13 +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>
<SpecificVersion>False</SpecificVersion>
<Private>False</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>
<SpecificVersion>False</SpecificVersion>
<Private>False</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
+24 -22
View File
@@ -21,15 +21,15 @@ namespace SqlCE4Umbraco
public class SqlCEInstaller : DefaultInstallerUtility<SqlCEHelper>
{
#region Private Constants
/// <summary>The latest database version this installer supports.</summary>
private const DatabaseVersion LatestVersionSupported = DatabaseVersion.Version4_8;
/// <summary>The specifications to determine the database version.</summary>
private static readonly VersionSpecs[] m_VersionSpecs = new VersionSpecs[] {
new VersionSpecs("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS LEFT OUTER JOIN umbracoApp ON appAlias = appAlias WHERE CONSTRAINT_NAME = 'FK_umbracoUser2app_umbracoApp'", 0, DatabaseVersion.Version4_8),
new VersionSpecs("SELECT id FROM umbracoNode WHERE id = -21", 1, DatabaseVersion.Version4_1),
new VersionSpecs("SELECT action FROM umbracoAppTree",DatabaseVersion.Version4),
new VersionSpecs("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS LEFT OUTER JOIN umbracoApp ON appAlias = appAlias WHERE CONSTRAINT_NAME = 'FK_umbracoUser2app_umbracoApp'", 0, DatabaseVersion.Version4_8),
new VersionSpecs("SELECT id FROM umbracoNode WHERE id = -21", 1, DatabaseVersion.Version4_1),
new VersionSpecs("SELECT action FROM umbracoAppTree",DatabaseVersion.Version4),
new VersionSpecs("SELECT description FROM cmsContentType",DatabaseVersion.Version3),
new VersionSpecs("SELECT id FROM sysobjects",DatabaseVersion.None) };
@@ -43,8 +43,9 @@ namespace SqlCE4Umbraco
public override bool CanConnect
{
get
{
SqlHelper.CreateEmptyDatabase();
{
using (var sqlHelper = SqlHelper)
sqlHelper.CreateEmptyDatabase();
return base.CanConnect;
}
}
@@ -93,22 +94,22 @@ namespace SqlCE4Umbraco
#region DefaultInstaller Members
/// <summary>
/// Returns the sql to do a full install
/// </summary>
protected override string FullInstallSql
{
get { return string.Empty; }
}
/// <summary>
/// Returns the sql to do a full install
/// </summary>
protected override string FullInstallSql
{
get { return string.Empty; }
}
/// <summary>
/// Returns the sql to do an upgrade
/// </summary>
protected override string UpgradeSql
{
get { return string.Empty; }
}
/// <summary>
/// Returns the sql to do an upgrade
/// </summary>
protected override string UpgradeSql
{
get { return string.Empty; }
}
// We need to override this as the default way of detection a db connection checks for systables that doesn't exist
// in a CE db
@@ -123,8 +124,9 @@ namespace SqlCE4Umbraco
// verify connection
try
{
if (SqlCeApplicationBlock.VerifyConnection(base.SqlHelper.ConnectionString))
return DatabaseVersion.None;
using (var sqlHelper = SqlHelper)
if (SqlCeApplicationBlock.VerifyConnection(sqlHelper.ConnectionString))
return DatabaseVersion.None;
}
catch (Exception e)
{
+17 -11
View File
@@ -37,13 +37,15 @@ namespace SqlCE4Umbraco
ITable table = null;
// get name in correct casing
name = SqlHelper.ExecuteScalar<string>("SELECT name FROM sys.tables WHERE name=@name",
SqlHelper.CreateParameter("name", name));
using (var sqlHelper = SqlHelper)
name = sqlHelper.ExecuteScalar<string>("SELECT name FROM sys.tables WHERE name=@name",
sqlHelper.CreateParameter("name", name));
if (name != null)
{
table = new DefaultTable(name);
using (IRecordsReader reader = SqlHelper.ExecuteReader(
using (var sqlHelper = SqlHelper)
using (IRecordsReader reader = sqlHelper.ExecuteReader(
@"SELECT c.name AS Name, st.name AS DataType, c.max_length, c.is_nullable, c.is_identity
FROM sys.tables AS t
JOIN sys.columns AS c ON t.object_id = c.object_id
@@ -51,7 +53,7 @@ namespace SqlCE4Umbraco
JOIN sys.types AS ty ON ty.user_type_id = c.user_type_id
JOIN sys.types st ON ty.system_type_id = st.user_type_id
WHERE t.name = @name
ORDER BY c.column_id", SqlHelper.CreateParameter("name", name)))
ORDER BY c.column_id", sqlHelper.CreateParameter("name", name)))
{
while (reader.Read())
{
@@ -112,7 +114,8 @@ namespace SqlCE4Umbraco
// create query
StringBuilder createTableQuery = new StringBuilder();
createTableQuery.AppendFormat("CREATE TABLE [{0}] (", SqlHelper.EscapeString(table.Name));
using (var sqlHelper = SqlHelper)
createTableQuery.AppendFormat("CREATE TABLE [{0}] (", sqlHelper.EscapeString(table.Name));
// add fields
while (hasNext)
@@ -136,7 +139,8 @@ namespace SqlCE4Umbraco
// execute query
try
{
SqlHelper.ExecuteNonQuery(createTableQuery.ToString());
using (var sqlHelper = SqlHelper)
sqlHelper.ExecuteNonQuery(createTableQuery.ToString());
}
catch (Exception executeException)
{
@@ -154,13 +158,15 @@ namespace SqlCE4Umbraco
Debug.Assert(table != null && field != null);
StringBuilder addColumnQuery = new StringBuilder();
addColumnQuery.AppendFormat("ALTER TABLE [{0}] ADD [{1}] {2}",
SqlHelper.EscapeString(table.Name),
SqlHelper.EscapeString(field.Name),
SqlHelper.EscapeString(GetDatabaseType(field)));
using (var sqlHelper = SqlHelper)
addColumnQuery.AppendFormat("ALTER TABLE [{0}] ADD [{1}] {2}",
sqlHelper.EscapeString(table.Name),
sqlHelper.EscapeString(field.Name),
sqlHelper.EscapeString(GetDatabaseType(field)));
try
{
SqlHelper.ExecuteNonQuery(addColumnQuery.ToString());
using (var sqlHelper = SqlHelper)
sqlHelper.ExecuteNonQuery(addColumnQuery.ToString());
}
catch (Exception executeException)
{
+3 -3
View File
@@ -2,7 +2,7 @@
using System.Resources;
[assembly: AssemblyCompany("Umbraco")]
[assembly: AssemblyCopyright("Copyright © Umbraco 2016")]
[assembly: AssemblyCopyright("Copyright © Umbraco 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.5.4")]
[assembly: AssemblyInformationalVersion("7.5.4")]
[assembly: AssemblyFileVersion("7.5.10")]
[assembly: AssemblyInformationalVersion("7.5.10")]
+2 -1
View File
@@ -64,7 +64,8 @@ namespace Umbraco.Core.Cache
[UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string ContentTypePropertiesCacheKey = "ContentType_PropertyTypes_Content:";
[UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
[Obsolete("No longer used and will be removed in v8")]
[EditorBrowsable(EditorBrowsableState.Never)]
public const string PropertyTypeCacheKey = "UmbracoPropertyTypeCache";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
@@ -4,8 +4,7 @@ using System.Configuration;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class ContentElement : ConfigurationElement, IContentSection
internal class ContentElement : UmbracoConfigurationElement, IContentSection
{
[ConfigurationProperty("imaging")]
internal ContentImagingElement Imaging
@@ -22,25 +21,13 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
[ConfigurationProperty("ResolveUrlsFromTextString")]
internal InnerTextConfigurationElement<bool> ResolveUrlsFromTextString
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["ResolveUrlsFromTextString"],
//set the default
false);
}
get { return GetOptionalTextElement<bool>("ResolveUrlsFromTextString", false); }
}
[ConfigurationProperty("UploadAllowDirectories")]
internal InnerTextConfigurationElement<bool> UploadAllowDirectories
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["UploadAllowDirectories"],
//set the default
true);
}
get { return GetOptionalTextElement("UploadAllowDirectories", true); }
}
public IEnumerable<IContentErrorPage> Error404Collection
@@ -63,121 +50,61 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
[ConfigurationProperty("ensureUniqueNaming")]
internal InnerTextConfigurationElement<bool> EnsureUniqueNaming
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["ensureUniqueNaming"],
//set the default
true);
}
get { return GetOptionalTextElement("ensureUniqueNaming", true); }
}
[ConfigurationProperty("TidyEditorContent")]
internal InnerTextConfigurationElement<bool> TidyEditorContent
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["TidyEditorContent"],
//set the default
false);
}
get { return GetOptionalTextElement("TidyEditorContent", false); }
}
[ConfigurationProperty("TidyCharEncoding")]
internal InnerTextConfigurationElement<string> TidyCharEncoding
{
get
{
return new OptionalInnerTextConfigurationElement<string>(
(InnerTextConfigurationElement<string>)this["TidyCharEncoding"],
//set the default
"UTF8");
}
get { return GetOptionalTextElement("TidyCharEncoding", "UTF8"); }
}
[ConfigurationProperty("XmlCacheEnabled")]
internal InnerTextConfigurationElement<bool> XmlCacheEnabled
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["XmlCacheEnabled"],
//set the default
true);
}
get { return GetOptionalTextElement("XmlCacheEnabled", true); }
}
[ConfigurationProperty("ContinouslyUpdateXmlDiskCache")]
internal InnerTextConfigurationElement<bool> ContinouslyUpdateXmlDiskCache
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["ContinouslyUpdateXmlDiskCache"],
//set the default
true);
}
get { return GetOptionalTextElement("ContinouslyUpdateXmlDiskCache", true); }
}
[ConfigurationProperty("XmlContentCheckForDiskChanges")]
internal InnerTextConfigurationElement<bool> XmlContentCheckForDiskChanges
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["XmlContentCheckForDiskChanges"],
//set the default
false);
}
get { return GetOptionalTextElement("XmlContentCheckForDiskChanges", false); }
}
[ConfigurationProperty("EnableSplashWhileLoading")]
internal InnerTextConfigurationElement<bool> EnableSplashWhileLoading
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["EnableSplashWhileLoading"],
//set the default
false);
}
get { return GetOptionalTextElement("EnableSplashWhileLoading", false); }
}
[ConfigurationProperty("PropertyContextHelpOption")]
internal InnerTextConfigurationElement<string> PropertyContextHelpOption
{
get
{
return new OptionalInnerTextConfigurationElement<string>(
(InnerTextConfigurationElement<string>)this["PropertyContextHelpOption"],
//set the default
"text");
}
get { return GetOptionalTextElement("PropertyContextHelpOption", "text"); }
}
[ConfigurationProperty("UseLegacyXmlSchema")]
internal InnerTextConfigurationElement<bool> UseLegacyXmlSchema
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["UseLegacyXmlSchema"],
//set the default
false);
}
get { return GetOptionalTextElement("UseLegacyXmlSchema", false); }
}
[ConfigurationProperty("ForceSafeAliases")]
internal InnerTextConfigurationElement<bool> ForceSafeAliases
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["ForceSafeAliases"],
//set the default
true);
}
get { return GetOptionalTextElement("ForceSafeAliases", true); }
}
[ConfigurationProperty("PreviewBadge")]
@@ -185,123 +112,63 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
{
get
{
return new OptionalInnerTextConfigurationElement<string>(
(InnerTextConfigurationElement<string>)this["PreviewBadge"],
//set the default
@"<a id=""umbracoPreviewBadge"" style=""position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{1}/preview/previewModeBadge.png') no-repeat;"" href=""{0}/endPreview.aspx?redir={2}""><span style=""display:none;"">In Preview Mode - click to end</span></a>");
return GetOptionalTextElement("PreviewBadge", @"<a id=""umbracoPreviewBadge"" style=""position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{1}/preview/previewModeBadge.png') no-repeat;"" href=""{0}/endPreview.aspx?redir={2}""><span style=""display:none;"">In Preview Mode - click to end</span></a>");
}
}
[ConfigurationProperty("UmbracoLibraryCacheDuration")]
internal InnerTextConfigurationElement<int> UmbracoLibraryCacheDuration
{
get
{
return new OptionalInnerTextConfigurationElement<int>(
(InnerTextConfigurationElement<int>)this["UmbracoLibraryCacheDuration"],
//set the default
1800);
}
get { return GetOptionalTextElement("UmbracoLibraryCacheDuration", 1800); }
}
[ConfigurationProperty("MacroErrors")]
internal InnerTextConfigurationElement<MacroErrorBehaviour> MacroErrors
{
get
{
return new OptionalInnerTextConfigurationElement<MacroErrorBehaviour>(
(InnerTextConfigurationElement<MacroErrorBehaviour>)this["MacroErrors"],
//set the default
MacroErrorBehaviour.Inline);
}
get { return GetOptionalTextElement("MacroErrors", MacroErrorBehaviour.Inline); }
}
[Obsolete("This is here so that if this config element exists we won't get a YSOD, it is not used whatsoever and will be removed in future versions")]
[ConfigurationProperty("DocumentTypeIconList")]
internal InnerTextConfigurationElement<IconPickerBehaviour> DocumentTypeIconList
{
get
{
return new OptionalInnerTextConfigurationElement<IconPickerBehaviour>(
(InnerTextConfigurationElement<IconPickerBehaviour>)this["DocumentTypeIconList"],
//set the default
IconPickerBehaviour.HideFileDuplicates);
}
get { return GetOptionalTextElement("DocumentTypeIconList", IconPickerBehaviour.HideFileDuplicates); }
}
[ConfigurationProperty("disallowedUploadFiles")]
internal CommaDelimitedConfigurationElement DisallowedUploadFiles
{
get
{
return new OptionalCommaDelimitedConfigurationElement(
(CommaDelimitedConfigurationElement)this["disallowedUploadFiles"],
//set the default
new[] { "ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd" });
}
get { return GetOptionalDelimitedElement("disallowedUploadFiles", new[] {"ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd"}); }
}
[ConfigurationProperty("cloneXmlContent")]
internal InnerTextConfigurationElement<bool> CloneXmlContent
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["cloneXmlContent"],
//set the default
true);
}
get { return GetOptionalTextElement("cloneXmlContent", true); }
}
[ConfigurationProperty("GlobalPreviewStorageEnabled")]
internal InnerTextConfigurationElement<bool> GlobalPreviewStorageEnabled
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["GlobalPreviewStorageEnabled"],
//set the default
false);
}
get { return GetOptionalTextElement("GlobalPreviewStorageEnabled", false); }
}
[ConfigurationProperty("defaultDocumentTypeProperty")]
internal InnerTextConfigurationElement<string> DefaultDocumentTypeProperty
{
get
{
return new OptionalInnerTextConfigurationElement<string>(
(InnerTextConfigurationElement<string>)this["defaultDocumentTypeProperty"],
//set the default
"Textstring");
}
get { return GetOptionalTextElement("defaultDocumentTypeProperty", "Textstring"); }
}
[ConfigurationProperty("EnableInheritedDocumentTypes")]
internal InnerTextConfigurationElement<bool> EnableInheritedDocumentTypes
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>) this["EnableInheritedDocumentTypes"],
//set the default
true);
}
get { return GetOptionalTextElement("EnableInheritedDocumentTypes", true); }
}
[ConfigurationProperty("EnableInheritedMediaTypes")]
internal InnerTextConfigurationElement<bool> EnableInheritedMediaTypes
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["EnableInheritedMediaTypes"],
//set the default
true);
}
get { return GetOptionalTextElement("EnableInheritedMediaTypes", true); }
}
string IContentSection.NotificationEmailAddress
@@ -3,42 +3,24 @@ using System.Configuration;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class ContentScriptEditorElement : ConfigurationElement
internal class ContentScriptEditorElement : UmbracoConfigurationElement
{
[ConfigurationProperty("scriptFolderPath")]
internal InnerTextConfigurationElement<string> ScriptFolderPath
{
get
{
return new OptionalInnerTextConfigurationElement<string>(
(InnerTextConfigurationElement<string>)this["scriptFolderPath"],
//set the default
"/scripts");
}
get { return GetOptionalTextElement("scriptFolderPath", "/scripts"); }
}
[ConfigurationProperty("scriptFileTypes")]
internal OptionalCommaDelimitedConfigurationElement ScriptFileTypes
{
get
{
return new OptionalCommaDelimitedConfigurationElement(
(OptionalCommaDelimitedConfigurationElement)this["scriptFileTypes"],
//set the default
new[] { "js", "xml" });
}
get { return GetOptionalDelimitedElement("scriptFileTypes", new[] {"js", "xml"}); }
}
[ConfigurationProperty("scriptDisableEditor")]
internal InnerTextConfigurationElement<bool> ScriptEditorDisable
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>) this["scriptDisableEditor"],
//set the default
false);
}
get { return GetOptionalTextElement("scriptDisableEditor", false); }
}
}
@@ -2,7 +2,7 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class ImagingAutoFillUploadFieldElement : ConfigurationElement, IImagingAutoFillUploadField
internal class ImagingAutoFillUploadFieldElement : UmbracoConfigurationElement, IImagingAutoFillUploadField
{
/// <summary>
/// Allow setting internally so we can create a default
@@ -17,49 +17,25 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
[ConfigurationProperty("widthFieldAlias")]
internal InnerTextConfigurationElement<string> WidthFieldAlias
{
get
{
return new OptionalInnerTextConfigurationElement<string>(
(InnerTextConfigurationElement<string>)this["widthFieldAlias"],
//set the default
"umbracoWidth");
}
get { return GetOptionalTextElement("widthFieldAlias", "umbracoWidth"); }
}
[ConfigurationProperty("heightFieldAlias")]
internal InnerTextConfigurationElement<string> HeightFieldAlias
{
get
{
return new OptionalInnerTextConfigurationElement<string>(
(InnerTextConfigurationElement<string>)this["heightFieldAlias"],
//set the default
"umbracoHeight");
}
get { return GetOptionalTextElement("heightFieldAlias", "umbracoHeight"); }
}
[ConfigurationProperty("lengthFieldAlias")]
internal InnerTextConfigurationElement<string> LengthFieldAlias
{
get
{
return new OptionalInnerTextConfigurationElement<string>(
(InnerTextConfigurationElement<string>)this["lengthFieldAlias"],
//set the default
"umbracoBytes");
}
get { return GetOptionalTextElement("lengthFieldAlias", "umbracoBytes"); }
}
[ConfigurationProperty("extensionFieldAlias")]
internal InnerTextConfigurationElement<string> ExtensionFieldAlias
{
get
{
return new OptionalInnerTextConfigurationElement<string>(
(InnerTextConfigurationElement<string>)this["extensionFieldAlias"],
//set the default
"umbracoExtension");
}
get { return GetOptionalTextElement("extensionFieldAlias", "umbracoExtension"); }
}
string IImagingAutoFillUploadField.Alias
@@ -3,67 +3,37 @@ using System.Configuration;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class LoggingElement : ConfigurationElement, ILoggingSection
internal class LoggingElement : UmbracoConfigurationElement, ILoggingSection
{
[ConfigurationProperty("autoCleanLogs")]
internal InnerTextConfigurationElement<bool> AutoCleanLogs
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["autoCleanLogs"],
//set the default
false);
}
get { return GetOptionalTextElement("autoCleanLogs", false); }
}
[ConfigurationProperty("enableLogging")]
internal InnerTextConfigurationElement<bool> EnableLogging
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["enableLogging"],
//set the default
true);
}
get { return GetOptionalTextElement("enableLogging", true); }
}
[ConfigurationProperty("enableAsyncLogging")]
internal InnerTextConfigurationElement<bool> EnableAsyncLogging
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["enableAsyncLogging"],
//set the default
true);
}
get { return GetOptionalTextElement("enableAsyncLogging", true); }
}
[ConfigurationProperty("cleaningMiliseconds")]
internal InnerTextConfigurationElement<int> CleaningMiliseconds
{
get
{
return new OptionalInnerTextConfigurationElement<int>(
(InnerTextConfigurationElement<int>)this["cleaningMiliseconds"],
//set the default
-1);
}
get { return GetOptionalTextElement("cleaningMiliseconds", -1); }
}
[ConfigurationProperty("maxLogAge")]
internal InnerTextConfigurationElement<int> MaxLogAge
{
get
{
return new OptionalInnerTextConfigurationElement<int>(
(InnerTextConfigurationElement<int>)this["maxLogAge"],
//set the default
-1);
}
get { return GetOptionalTextElement("maxLogAge", -1); }
}
[ConfigurationCollection(typeof(DisabledLogTypesCollection), AddItemName = "logTypeAlias")]
@@ -2,7 +2,7 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class NotificationsElement : ConfigurationElement
internal class NotificationsElement : UmbracoConfigurationElement
{
[ConfigurationProperty("email")]
internal InnerTextConfigurationElement<string> NotificationEmailAddress
@@ -13,13 +13,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
[ConfigurationProperty("disableHtmlEmail")]
internal InnerTextConfigurationElement<bool> DisableHtmlEmail
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>) this["disableHtmlEmail"],
//set the default
false);
}
get { return GetOptionalTextElement("disableHtmlEmail", false); }
}
}
@@ -5,30 +5,18 @@ using System.Collections.Generic;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class RequestHandlerElement : ConfigurationElement, IRequestHandlerSection
internal class RequestHandlerElement : UmbracoConfigurationElement, IRequestHandlerSection
{
[ConfigurationProperty("useDomainPrefixes")]
public InnerTextConfigurationElement<bool> UseDomainPrefixes
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["useDomainPrefixes"],
//set the default
false);
}
get { return GetOptionalTextElement("useDomainPrefixes", false); }
}
[ConfigurationProperty("addTrailingSlash")]
public InnerTextConfigurationElement<bool> AddTrailingSlash
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["addTrailingSlash"],
//set the default
true);
}
get { return GetOptionalTextElement("addTrailingSlash", true); }
}
private UrlReplacingElement _defaultUrlReplacing;
@@ -2,66 +2,36 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class SecurityElement : ConfigurationElement, ISecuritySection
internal class SecurityElement : UmbracoConfigurationElement, ISecuritySection
{
[ConfigurationProperty("keepUserLoggedIn")]
internal InnerTextConfigurationElement<bool> KeepUserLoggedIn
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["keepUserLoggedIn"],
//set the default
true);
}
get { return GetOptionalTextElement("keepUserLoggedIn", true); }
}
[ConfigurationProperty("hideDisabledUsersInBackoffice")]
internal InnerTextConfigurationElement<bool> HideDisabledUsersInBackoffice
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["hideDisabledUsersInBackoffice"],
//set the default
false);
}
get { return GetOptionalTextElement("hideDisabledUsersInBackoffice", false); }
}
[ConfigurationProperty("allowPasswordReset")]
internal InnerTextConfigurationElement<bool> AllowPasswordReset
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["allowPasswordReset"],
//set the default
true);
}
get { return GetOptionalTextElement("allowPasswordReset", true); }
}
[ConfigurationProperty("authCookieName")]
internal InnerTextConfigurationElement<string> AuthCookieName
{
get
{
return new OptionalInnerTextConfigurationElement<string>(
(InnerTextConfigurationElement<string>)this["authCookieName"],
//set the default
Constants.Web.AuthCookieName);
}
get { return GetOptionalTextElement("authCookieName", Constants.Web.AuthCookieName); }
}
[ConfigurationProperty("authCookieDomain")]
internal InnerTextConfigurationElement<string> AuthCookieDomain
{
get
{
return new OptionalInnerTextConfigurationElement<string>(
(InnerTextConfigurationElement<string>)this["authCookieDomain"],
//set the default
null);
}
get { return GetOptionalTextElement<string>("authCookieDomain", null); }
}
bool ISecuritySection.KeepUserLoggedIn
@@ -3,55 +3,31 @@ using System.Configuration;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class TemplatesElement : ConfigurationElement, ITemplatesSection
internal class TemplatesElement : UmbracoConfigurationElement, ITemplatesSection
{
[ConfigurationProperty("useAspNetMasterPages")]
internal InnerTextConfigurationElement<bool> UseAspNetMasterPages
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["useAspNetMasterPages"],
//set the default
true);
}
get { return GetOptionalTextElement("useAspNetMasterPages", true); }
}
[ConfigurationProperty("enableSkinSupport")]
internal InnerTextConfigurationElement<bool> EnableSkinSupport
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["enableSkinSupport"],
//set the default
true);
}
get { return GetOptionalTextElement("enableSkinSupport", true); }
}
[ConfigurationProperty("defaultRenderingEngine", IsRequired = true)]
internal InnerTextConfigurationElement<RenderingEngine> DefaultRenderingEngine
{
get
{
return new OptionalInnerTextConfigurationElement<RenderingEngine>(
(InnerTextConfigurationElement<RenderingEngine>)this["defaultRenderingEngine"],
//set the default
RenderingEngine.Mvc);
}
get { return GetOptionalTextElement("defaultRenderingEngine", RenderingEngine.Mvc); }
}
[Obsolete("This has no affect and will be removed in future versions")]
[ConfigurationProperty("enableTemplateFolders")]
internal InnerTextConfigurationElement<bool> EnableTemplateFolders
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["enableTemplateFolders"],
//set the default
false);
}
get { return GetOptionalTextElement("enableTemplateFolders", false); }
}
bool ITemplatesSection.UseAspNetMasterPages
@@ -0,0 +1,36 @@
using System.Collections.Concurrent;
using System.Configuration;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
/// <summary>
/// Base class with shared helper methods
/// </summary>
internal class UmbracoConfigurationElement : ConfigurationElement
{
/// <summary>
/// Used so the RawElement types are not re-created every time they are accessed
/// </summary>
private readonly ConcurrentDictionary<string, RawXmlConfigurationElement> _rawElements = new ConcurrentDictionary<string, RawXmlConfigurationElement>();
protected OptionalInnerTextConfigurationElement<T> GetOptionalTextElement<T>(string name, T defaultVal)
{
return (OptionalInnerTextConfigurationElement<T>) _rawElements.GetOrAdd(
name,
s => new OptionalInnerTextConfigurationElement<T>(
(InnerTextConfigurationElement<T>) this[s],
//set the default
defaultVal));
}
protected OptionalCommaDelimitedConfigurationElement GetOptionalDelimitedElement(string name, string[] defaultVal)
{
return (OptionalCommaDelimitedConfigurationElement) _rawElements.GetOrAdd(
name,
s => new OptionalCommaDelimitedConfigurationElement(
(CommaDelimitedConfigurationElement) this[name],
//set the default
defaultVal));
}
}
}
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
{
public class UmbracoVersion
{
private static readonly Version Version = new Version("7.5.4");
private static readonly Version Version = new Version("7.5.10");
/// <summary>
/// Gets the current version of Umbraco.
@@ -122,6 +122,11 @@ namespace Umbraco.Core
/// MediaType alias for an image.
/// </summary>
public const string Image = "Image";
/// <summary>
/// MediaType alias indicating allowing auto-selection.
/// </summary>
public const string AutoSelect = "umbracoAutoSelect";
}
/// <summary>
+17 -12
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Threading;
@@ -30,12 +31,13 @@ using Umbraco.Core.Manifest;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
using Umbraco.Core.Strings;
using IntegerValidator = Umbraco.Core.PropertyEditors.IntegerValidator;
using MigrationsVersionFourNineZero = Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionFourNineZero;
namespace Umbraco.Core
{
/// <summary>
/// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application
/// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application
/// </summary>
/// <remarks>
/// This does not provide any startup functionality relating to web objects
@@ -191,14 +193,14 @@ namespace Umbraco.Core
protected virtual CacheHelper CreateApplicationCache()
{
var cacheHelper = new CacheHelper(
//we need to have the dep clone runtime cache provider to ensure
//we need to have the dep clone runtime cache provider to ensure
//all entities are cached properly (cloned in and cloned out)
new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()),
new StaticCacheProvider(),
//we have no request based cache when not running in web-based context
new NullCacheProvider(),
new IsolatedRuntimeCache(type =>
//we need to have the dep clone runtime cache provider to ensure
//we need to have the dep clone runtime cache provider to ensure
//all entities are cached properly (cloned in and cloned out)
new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider())));
@@ -251,18 +253,18 @@ namespace Umbraco.Core
}
/// <summary>
/// Special method to initialize the ApplicationEventsResolver and any modifications required for it such
/// Special method to initialize the ApplicationEventsResolver and any modifications required for it such
/// as adding custom types to the resolver.
/// </summary>
protected virtual void InitializeApplicationEventsResolver()
{
//find and initialize the application startup handlers, we need to initialize this resolver here because
//it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to
//it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to
//events and to call their events during bootup.
//ApplicationStartupHandler.RegisterHandlers();
//... and set the special flag to let us resolve before frozen resolution
ApplicationEventsResolver.Current = new ApplicationEventsResolver(
ServiceProvider,
ServiceProvider,
ProfilingLogger.Logger,
PluginManager.ResolveApplicationStartupHandlers())
{
@@ -282,7 +284,7 @@ namespace Umbraco.Core
}
/// <summary>
/// Fires after initialization and calls the callback to allow for customizations to occur &
/// Fires after initialization and calls the callback to allow for customizations to occur &
/// Ensure that the OnApplicationStarting methods of the IApplicationEvents are called
/// </summary>
/// <param name="afterStartup"></param>
@@ -334,7 +336,7 @@ namespace Umbraco.Core
{
if (_isComplete)
throw new InvalidOperationException("The boot manager has already been completed");
FreezeResolution();
//Here we need to make sure the db can be connected to
@@ -366,7 +368,7 @@ namespace Umbraco.Core
ProfilingLogger.Logger.Error<CoreBootManager>("An error occurred running OnApplicationStarted for handler " + x.GetType(), ex);
throw;
}
});
});
}
//Now, startup all of our legacy startup handler
@@ -413,7 +415,7 @@ namespace Umbraco.Core
if (currentTry == 5)
{
throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but the Umbraco cannot connect to the database.");
throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but Umbraco cannot connect to the database.");
}
}
@@ -455,6 +457,10 @@ namespace Umbraco.Core
{
ServerRegistrarResolver.Current = new ServerRegistrarResolver(new ConfigServerRegistrar());
}
else if ("true".InvariantEquals(ConfigurationManager.AppSettings["umbracoDisableElectionForSingleServer"]))
{
ServerRegistrarResolver.Current = new ServerRegistrarResolver(new SingleServerRegistrar());
}
else
{
ServerRegistrarResolver.Current = new ServerRegistrarResolver(
@@ -462,7 +468,6 @@ namespace Umbraco.Core
new Lazy<IServerRegistrationService>(() => ApplicationContext.Services.ServerRegistrationService),
new DatabaseServerRegistrarOptions()));
}
//by default we'll use the database server messenger with default options (no callbacks),
// this will be overridden in the web startup
@@ -473,7 +478,7 @@ namespace Umbraco.Core
ServiceProvider, ProfilingLogger.Logger,
() => PluginManager.ResolveAssignedMapperTypes());
//RepositoryResolver.Current = new RepositoryResolver(
// new RepositoryFactory(ApplicationCache));
+30 -14
View File
@@ -34,6 +34,12 @@ namespace Umbraco.Core
private string _connectionString;
private string _providerName;
private DatabaseSchemaResult _result;
private DateTime? _connectionLastChecked = null;
/// <summary>
/// The number of minutes to throttle the checks to CanConnect
/// </summary>
private const int ConnectionCheckMinutes = 1;
[Obsolete("Use the constructor specifying all dependencies instead")]
public DatabaseContext(IDatabaseFactory factory)
@@ -110,13 +116,28 @@ namespace Umbraco.Core
{
get
{
if (IsDatabaseConfigured == false) return false;
var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider);
LogHelper.Info<DatabaseContext>("CanConnect = " + canConnect);
return canConnect;
if (IsDatabaseConfigured == false)
return false;
//Don't check again if the timeout period hasn't elapsed
//this ensures we don't keep checking the connection too many times in a row like during startup.
//Do check if the _connectionLastChecked is null which means we're just initializing or it could
//not connect last time it was checked.
if ((_connectionLastChecked.HasValue && (DateTime.Now - _connectionLastChecked.Value).TotalMinutes > ConnectionCheckMinutes)
|| _connectionLastChecked.HasValue == false)
{
var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider);
LogHelper.Info<DatabaseContext>("CanConnect = " + canConnect);
_connectionLastChecked = canConnect == false ? null : (DateTime?) DateTime.Now;
return canConnect;
}
return _connectionLastChecked.HasValue;
}
}
/// <summary>
/// Gets the configured umbraco db connection string.
/// </summary>
@@ -174,7 +195,7 @@ namespace Umbraco.Core
/// </summary>
public void ConfigureEmbeddedDatabaseConnection()
{
const string providerName = "System.Data.SqlServerCe.4.0";
const string providerName = Constants.DatabaseProviders.SqlCe;
var connectionString = GetEmbeddedDatabaseConnectionString();
SaveConnectionString(connectionString, providerName);
@@ -182,15 +203,10 @@ namespace Umbraco.Core
var path = Path.Combine(GlobalSettings.FullpathToRoot, "App_Data", "Umbraco.sdf");
if (File.Exists(path) == false)
{
var engine = new SqlCeEngine(connectionString);
engine.CreateDatabase();
// SD: Pretty sure this should be in a using clause but i don't want to cause unknown side-effects here
// since it's been like this for quite some time
//using (var engine = new SqlCeEngine(connectionString))
//{
// engine.CreateDatabase();
//}
using (var engine = new SqlCeEngine(connectionString))
{
engine.CreateDatabase();
}
}
Initialize(providerName);
@@ -1,9 +1,9 @@
namespace Umbraco.Core.Logging
{
using System;
using System.Runtime.CompilerServices;
using System;
using System.Runtime.CompilerServices;
using ImageProcessor.Common.Exceptions;
using ImageProcessor.Common.Exceptions;
namespace Umbraco.Core.Logging
{
/// <summary>
/// A logger for explicitly logging ImageProcessor exceptions.
+60 -22
View File
@@ -1,18 +1,21 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.MemoryMappedFiles;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Core.ObjectResolution;
namespace Umbraco.Core
{
// represents the main domain
class MainDom : IRegisteredObject
/// <summary>
/// Represents the main AppDomain running for a given application.
/// </summary>
/// <remarks>
/// <para>There can be only one "main" AppDomain running for a given application at a time.</para>
/// <para>When an AppDomain starts, it tries to acquire the main domain status.</para>
/// <para>When an AppDomain stops (eg the application is restarting) it should release the main domain status.</para>
/// <para>It is possible to register against the MainDom and be notified when it is released.</para>
/// </remarks>
internal class MainDom : IRegisteredObject
{
#region Vars
@@ -25,7 +28,7 @@ namespace Umbraco.Core
private readonly AsyncLock _asyncLock;
private IDisposable _asyncLocker;
// event wait handle used to notify current main domain that it should
// event wait handle used to notify current main domain that it should
// release the lock because a new domain wants to be the main domain
private readonly EventWaitHandle _signal;
@@ -34,16 +37,26 @@ namespace Umbraco.Core
private volatile bool _signaled; // we have been signaled
// actions to run before releasing the main domain
private readonly SortedList<int, Action> _callbacks = new SortedList<int, Action>();
private readonly SortedList<int, Action> _callbacks = new SortedList<int, Action>(new WeightComparer());
private const int LockTimeoutMilliseconds = 90000; // (1.5 * 60 * 1000) == 1 min 30 seconds
private class WeightComparer : IComparer<int>
{
public int Compare(int x, int y)
{
var result = x.CompareTo(y);
// return "equal" as "greater than"
return result == 0 ? 1 : result;
}
}
#endregion
#region Ctor
// initializes a new instance of MainDom
public MainDom(ILogger logger)
internal MainDom(ILogger logger)
{
_logger = logger;
@@ -52,22 +65,47 @@ namespace Umbraco.Core
if (HostingEnvironment.ApplicationID != null)
appId = HostingEnvironment.ApplicationID.ReplaceNonAlphanumericChars(string.Empty);
var lockName = "UMBRACO-" + appId + "-MAINDOM-LCK";
// combining with the physical path because if running on eg IIS Express,
// two sites could have the same appId even though they are different.
//
// now what could still collide is... two sites, running in two different processes
// and having the same appId, and running on the same app physical path
//
// we *cannot* use the process ID here because when an AppPool restarts it is
// a new process for the same application path
var appPath = HostingEnvironment.ApplicationPhysicalPath;
var hash = (appId + ":::" + appPath).ToSHA1();
var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK";
_asyncLock = new AsyncLock(lockName);
var eventName = "UMBRACO-" + appId + "-MAINDOM-EVT";
var eventName = "UMBRACO-" + hash + "-MAINDOM-EVT";
_signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
}
#endregion
// register a main domain consumer
/// <summary>
/// Registers a resource that requires the current AppDomain to be the main domain to function.
/// </summary>
/// <param name="release">An action to execute before the AppDomain releases the main domain status.</param>
/// <param name="weight">An optional weight (lower goes first).</param>
/// <returns>A value indicating whether it was possible to register.</returns>
public bool Register(Action release, int weight = 100)
{
return Register(null, release, weight);
}
// register a main domain consumer
/// <summary>
/// Registers a resource that requires the current AppDomain to be the main domain to function.
/// </summary>
/// <param name="install">An action to execute when registering.</param>
/// <param name="release">An action to execute before the AppDomain releases the main domain status.</param>
/// <param name="weight">An optional weight (lower goes first).</param>
/// <returns>A value indicating whether it was possible to register.</returns>
/// <remarks>If registering is successful, then the <paramref name="install"/> action
/// is guaranteed to execute before the AppDomain releases the main domain status.</remarks>
public bool Register(Action install, Action release, int weight = 100)
{
lock (_locko)
@@ -97,7 +135,7 @@ namespace Umbraco.Core
try
{
_logger.Debug<MainDom>("Stopping...");
_logger.Info<MainDom>("Stopping...");
foreach (var callback in _callbacks.Values)
{
try
@@ -109,7 +147,7 @@ namespace Umbraco.Core
_logger.Error<MainDom>("Error while running callback, remaining callbacks will not run.", e);
throw;
}
}
_logger.Debug<MainDom>("Stopped.");
}
@@ -118,12 +156,12 @@ namespace Umbraco.Core
// in any case...
_isMainDom = false;
_asyncLocker.Dispose();
_logger.Debug<MainDom>("Released MainDom.");
_logger.Info<MainDom>("Released MainDom.");
}
}
// acquires the main domain
public bool Acquire()
internal bool Acquire()
{
lock (_locko) // we don't want the hosting environment to interfere by signaling
{
@@ -131,11 +169,11 @@ namespace Umbraco.Core
// the handler is not installed so that would be the hosting environment
if (_signaled)
{
_logger.Debug<MainDom>("Cannot acquire MainDom (signaled).");
_logger.Info<MainDom>("Cannot acquire MainDom (signaled).");
return false;
}
_logger.Debug<MainDom>("Acquiring MainDom...");
_logger.Info<MainDom>("Acquiring MainDom...");
// signal other instances that we want the lock, then wait one the lock,
// which may timeout, and this is accepted - see comments below
@@ -162,7 +200,7 @@ namespace Umbraco.Core
HostingEnvironment.RegisterObject(this);
_logger.Debug<MainDom>("Acquired MainDom.");
_logger.Info<MainDom>("Acquired MainDom.");
return true;
}
}
@@ -174,7 +212,7 @@ namespace Umbraco.Core
}
// IRegisteredObject
public void Stop(bool immediate)
void IRegisteredObject.Stop(bool immediate)
{
try
{
+1 -2
View File
@@ -404,8 +404,7 @@ namespace Umbraco.Core.Models
return content.Properties
.Where(property => propertyGroup.PropertyTypes
.Select(propertyType => propertyType.Id)
.Contains(property.PropertyTypeId))
.OrderBy(x => x.PropertyType.SortOrder);
.Contains(property.PropertyTypeId));
}
/// <summary>
+1 -1
View File
@@ -326,7 +326,7 @@ namespace Umbraco.Core.Models
}
}
/// <summary>
/// <summary>
/// A boolean flag indicating if a property type has been removed from this instance.
/// </summary>
/// <remarks>
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a ContentType, which Media is based on
/// </summary
/// </summary>
public interface IMediaType : IContentTypeComposition
{
@@ -13,6 +13,7 @@ namespace Umbraco.Core.Models.Identity
public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext)
{
config.CreateMap<IUser, BackOfficeIdentityUser>()
.ForMember(user => user.LastLoginDateUtc, expression => expression.MapFrom(user => user.LastLoginDate.ToUniversalTime()))
.ForMember(user => user.Email, expression => expression.MapFrom(user => user.Email))
.ForMember(user => user.Id, expression => expression.MapFrom(user => user.Id))
.ForMember(user => user.LockoutEndDateUtc, expression => expression.MapFrom(user => user.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null))
@@ -24,12 +24,17 @@ namespace Umbraco.Core.Models.Identity
///
/// </summary>
public IdentityUser()
{
{
this.Claims = new List<TClaim>();
this.Roles = new List<TRole>();
this.Logins = new List<TLogin>();
}
/// <summary>
/// Last login date
/// </summary>
public virtual DateTime? LastLoginDateUtc { get; set; }
/// <summary>
/// Email
///
+4 -7
View File
@@ -25,7 +25,7 @@ namespace Umbraco.Core.Models
NoAccessNodeId = noAccessNode.Id;
_protectedNodeId = protectedNode.Id;
_ruleCollection = new ObservableCollection<PublicAccessRule>(ruleCollection);
_ruleCollection = new ObservableCollection<PublicAccessRule>(ruleCollection);
_ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged;
}
@@ -81,7 +81,7 @@ namespace Umbraco.Core.Models
internal IEnumerable<Guid> RemovedRules
{
get { return _removedRules; }
}
}
public IEnumerable<PublicAccessRule> Rules
{
@@ -107,10 +107,7 @@ namespace Umbraco.Core.Models
public void ClearRules()
{
for (var i = _ruleCollection.Count - 1; i >= 0; i--)
{
RemoveRule(_ruleCollection[i]);
}
_ruleCollection.Clear();
}
[DataMember]
@@ -126,7 +123,7 @@ namespace Umbraco.Core.Models
get { return _noAccessNodeId; }
set { SetPropertyValueAndDetectChanges(value, ref _noAccessNodeId, Ps.Value.NoAccessNodeIdSelector); }
}
[DataMember]
public int ProtectedNodeId
{
@@ -106,12 +106,13 @@ namespace Umbraco.Core.Models.PublishedContent
/// <param name="propertyTypeAlias">The property type alias.</param>
/// <param name="dataTypeDefinitionId">The datatype definition identifier.</param>
/// <param name="propertyEditorAlias">The property editor alias.</param>
/// <param name="initConverters">Generally used only for testing, in production this will always be true</param>
/// <remarks>
/// <para>The new published property type does not belong to a published content type.</para>
/// <para>The values of <paramref name="dataTypeDefinitionId"/> and <paramref name="propertyEditorAlias"/> are
/// assumed to be valid and consistent.</para>
/// </remarks>
internal PublishedPropertyType(string propertyTypeAlias, int dataTypeDefinitionId, string propertyEditorAlias)
internal PublishedPropertyType(string propertyTypeAlias, int dataTypeDefinitionId, string propertyEditorAlias, bool initConverters = true)
{
// ContentType
// - in unit tests, to be set by PublishedContentType when creating it
@@ -122,7 +123,8 @@ namespace Umbraco.Core.Models.PublishedContent
DataTypeId = dataTypeDefinitionId;
PropertyEditorAlias = propertyEditorAlias;
InitializeConverters();
if (initConverters)
InitializeConverters();
}
#endregion
@@ -11,7 +11,6 @@ namespace Umbraco.Core.Models.Rdbms
{
[Column("id")]
[PrimaryKeyColumn]
[Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyData")]
public int Id { get; set; }
[Column("contentNodeId")]
@@ -1,15 +1,117 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.UI.WebControls;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Models
{
internal static class UmbracoEntityExtensions
{
/// <summary>
/// Does a quick check on the entity's set path to ensure that it's valid and consistent
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static void ValidatePathWithException(this NodeDto entity)
{
//don't validate if it's empty and it has no id
if (entity.NodeId == default(int) && entity.Path.IsNullOrWhiteSpace())
return;
if (entity.Path.IsNullOrWhiteSpace())
throw new InvalidDataException(string.Format("The content item {0} has an empty path: {1} with parentID: {2}", entity.NodeId, entity.Path, entity.ParentId));
var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (pathParts.Length < 2)
{
//a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id
throw new InvalidDataException(string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", entity.NodeId, entity.Path, entity.ParentId));
}
if (entity.ParentId != default(int) && pathParts[pathParts.Length - 2] != entity.ParentId.ToInvariantString())
{
//the 2nd last id in the path must be it's parent id
throw new InvalidDataException(string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", entity.NodeId, entity.Path, entity.ParentId));
}
}
/// <summary>
/// Does a quick check on the entity's set path to ensure that it's valid and consistent
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static bool ValidatePath(this IUmbracoEntity entity)
{
//don't validate if it's empty and it has no id
if (entity.HasIdentity == false && entity.Path.IsNullOrWhiteSpace())
return true;
if (entity.Path.IsNullOrWhiteSpace())
return false;
var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (pathParts.Length < 2)
{
//a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id
return false;
}
if (entity.ParentId != default(int) && pathParts[pathParts.Length - 2] != entity.ParentId.ToInvariantString())
{
//the 2nd last id in the path must be it's parent id
return false;
}
return true;
}
/// <summary>
/// This will validate the entity's path and if it's invalid it will fix it, if fixing is required it will recursively
/// check and fix all ancestors if required.
/// </summary>
/// <param name="entity"></param>
/// <param name="logger"></param>
/// <param name="getParent">A callback specified to retrieve the parent entity of the entity</param>
/// <param name="update">A callback specified to update a fixed entity</param>
public static void EnsureValidPath<T>(this T entity,
ILogger logger,
Func<T, T> getParent,
Action<T> update)
where T: IUmbracoEntity
{
if (entity.HasIdentity == false)
throw new InvalidOperationException("Could not ensure the entity path, the entity has not been assigned an identity");
if (entity.ValidatePath() == false)
{
logger.Warn(typeof(UmbracoEntityExtensions), string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", entity.Id, entity.Path, entity.ParentId));
if (entity.ParentId == -1)
{
entity.Path = string.Concat("-1,", entity.Id);
//path changed, update it
update(entity);
}
else
{
var parent = getParent(entity);
if (parent == null)
throw new NullReferenceException("Could not ensure path for entity " + entity.Id + " could not resolve it's parent " + entity.ParentId);
//the parent must also be valid!
parent.EnsureValidPath(logger, getParent, update);
entity.Path = string.Concat(parent.Path, ",", entity.Id);
//path changed, update it
update(entity);
}
}
}
public static bool HasChildren(this IUmbracoEntity entity)
{
@@ -7,15 +7,20 @@ using umbraco.interfaces;
namespace Umbraco.Core.ObjectResolution
{
/// <summary>
/// <summary>
/// A resolver to return all IApplicationEvents objects
/// </summary>
/// <remarks>
/// This is disposable because after the app has started it should be disposed to release any memory being occupied by instances.
/// <para>This is disposable because after the app has started it should be disposed to release any memory being occupied by instances.</para>
/// <para>Ordering handlers: handlers are ordered by (ascending) weight. By default, handlers from the Umbraco.* or Concorde.*
/// assemblies have a -100 weight whereas any other handler has a weight of +100. A custom weight can be assigned to a handler
/// by marking the class with the WeightAttribute. For example, the CacheRefresherEventHandler is marked with [Weight(int.MinValue)]
/// because its events need to run before anything else. Positive weights are considered "user-space" while negative weights are
/// "core". Finally, users can register a filter to process the list (after it has been ordered) and re-order it, or remove handlers.</para>
/// <para>BEWARE! handlers order is an important thing, and removing handlers or reordering handlers can have unexpected consequences.</para>
/// </remarks>
internal sealed class ApplicationEventsResolver : ManyObjectsResolverBase<ApplicationEventsResolver, IApplicationEventHandler>, IDisposable
public sealed class ApplicationEventsResolver : ManyObjectsResolverBase<ApplicationEventsResolver, IApplicationEventHandler>, IDisposable
{
private readonly LegacyStartupHandlerResolver _legacyResolver;
/// <summary>
@@ -23,18 +28,18 @@ namespace Umbraco.Core.ObjectResolution
/// </summary>
/// <param name="logger"></param>
/// <param name="applicationEventHandlers"></param>
/// <param name="serviceProvider"></param>
/// <param name="serviceProvider"></param>
internal ApplicationEventsResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable<Type> applicationEventHandlers)
: base(serviceProvider, logger, applicationEventHandlers)
{
{
//create the legacy resolver and only include the legacy types
_legacyResolver = new LegacyStartupHandlerResolver(
_legacyResolver = new LegacyStartupHandlerResolver(
serviceProvider, logger,
applicationEventHandlers.Where(x => !TypeHelper.IsTypeAssignableFrom<IApplicationEventHandler>(x)));
applicationEventHandlers.Where(x => TypeHelper.IsTypeAssignableFrom<IApplicationEventHandler>(x) == false));
}
/// <summary>
/// Override in order to only return types of IApplicationEventHandler and above,
/// Override in order to only return types of IApplicationEventHandler and above,
/// do not include the legacy types of IApplicationStartupHandler
/// </summary>
protected override IEnumerable<Type> InstanceTypes
@@ -42,14 +47,60 @@ namespace Umbraco.Core.ObjectResolution
get { return base.InstanceTypes.Where(TypeHelper.IsTypeAssignableFrom<IApplicationEventHandler>); }
}
/// <summary>
/// Gets the <see cref="IApplicationEventHandler"/> implementations.
/// </summary>
public IEnumerable<IApplicationEventHandler> ApplicationEventHandlers
private List<IApplicationEventHandler> _orderedAndFiltered;
/// <summary>
/// Gets the <see cref="IApplicationEventHandler"/> implementations.
/// </summary>
public IEnumerable<IApplicationEventHandler> ApplicationEventHandlers
{
get { return Values; }
get
{
if (_orderedAndFiltered == null)
{
_orderedAndFiltered = GetSortedValues().ToList();
if (FilterCollection != null)
FilterCollection(_orderedAndFiltered);
}
return _orderedAndFiltered;
}
}
/// <summary>
/// Gets or sets a delegate to filter the event handler list (EXPERT!).
/// </summary>
/// <remarks>
/// <para>This can be set on startup in the pre-boot process in either a custom boot manager or global.asax (UmbracoApplication).</para>
/// <para>Allows custom logic to execute in order to filter and/or re-order the event handlers prior to executing.</para>
/// <para>To be used by custom boot sequences where the boot loader needs to remove some handlers, or raise their priority.</para>
/// <para>Filtering the event handler collection can have ugly consequences. Use with care.</para>
/// </remarks>
public Action<IList<IApplicationEventHandler>> FilterCollection
{
get { return _filterCollection; }
set
{
if (_orderedAndFiltered != null)
throw new InvalidOperationException("Cannot set the FilterCollection delegate once the ApplicationEventHandlers are resolved");
if (_filterCollection != null)
throw new InvalidOperationException("Cannot set the FilterCollection delegate once it's already been specified");
_filterCollection = value;
}
}
protected override int GetObjectWeight(object o)
{
var type = o.GetType();
var attr = type.GetCustomAttribute<WeightAttribute>(true);
if (attr != null) return attr.Weight;
var name = type.Assembly.FullName;
// we should really attribute all our Core handlers, so this is temp
var core = name.InvariantStartsWith("Umbraco.") || name.InvariantStartsWith("Concorde.");
return core ? -DefaultPluginWeight : DefaultPluginWeight;
}
/// <summary>
/// Create instances of all of the legacy startup handlers
/// </summary>
@@ -62,11 +113,11 @@ namespace Umbraco.Core.ObjectResolution
protected override bool SupportsClear
{
get { return false; }
}
}
protected override bool SupportsInsert
{
get { return false; }
get { return false; }
}
private class LegacyStartupHandlerResolver : ManyObjectsResolverBase<ApplicationEventsResolver, IApplicationStartupHandler>, IDisposable
@@ -90,8 +141,9 @@ namespace Umbraco.Core.ObjectResolution
private bool _disposed;
private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim();
private Action<IList<IApplicationEventHandler>> _filterCollection;
/// <summary>
/// <summary>
/// Gets a value indicating whether this instance is disposed.
/// </summary>
/// <value>
@@ -146,7 +198,8 @@ namespace Umbraco.Core.ObjectResolution
{
_legacyResolver.Dispose();
ResetCollections();
_orderedAndFiltered.Clear();
_orderedAndFiltered = null;
}
}
}
}
@@ -17,7 +17,7 @@ namespace Umbraco.Core.ObjectResolution
/// for when there is some processing overhead (i.e. Type finding in assemblies) to return the Types used to instantiate the instances.
/// In some these cases we don't want to have to type-find during application startup, only when we need to resolve the instances.</para>
/// <para>Important notes about this resolver: it does not support Insert or Remove and therefore does not support any ordering unless
/// the types are marked with the WeightedPluginAttribute.</para>
/// the types are marked with the WeightAttribute.</para>
/// </remarks>
public abstract class LazyManyObjectsResolverBase<TResolver, TResolved> : ManyObjectsResolverBase<TResolver, TResolved>
where TResolved : class
@@ -14,18 +14,18 @@ namespace Umbraco.Core.ObjectResolution
/// <typeparam name="TResolver">The type of the concrete resolver class.</typeparam>
/// <typeparam name="TResolved">The type of the resolved objects.</typeparam>
public abstract class ManyObjectsResolverBase<TResolver, TResolved> : ResolverBase<TResolver>
where TResolved : class
where TResolved : class
where TResolver : ResolverBase
{
private Lazy<IEnumerable<TResolved>> _applicationInstances;
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private readonly string _httpContextKey;
private readonly List<Type> _instanceTypes = new List<Type>();
private IEnumerable<TResolved> _sortedValues;
{
private Lazy<IEnumerable<TResolved>> _applicationInstances;
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private readonly string _httpContextKey;
private readonly List<Type> _instanceTypes = new List<Type>();
private IEnumerable<TResolved> _sortedValues;
private int _defaultPluginWeight = 10;
private int _defaultPluginWeight = 100;
#region Constructors
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ManyObjectsResolverBase{TResolver, TResolved}"/> class with an empty list of objects,
@@ -61,11 +61,11 @@ namespace Umbraco.Core.ObjectResolution
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use ctor specifying IServiceProvider instead")]
protected ManyObjectsResolverBase(ObjectLifetimeScope scope = ObjectLifetimeScope.Application)
protected ManyObjectsResolverBase(ObjectLifetimeScope scope = ObjectLifetimeScope.Application)
: this(new ActivatorServiceProvider(), LoggerResolver.Current.Logger, scope)
{
}
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ManyObjectsResolverBase{TResolver, TResolved}"/> class with an empty list of objects,
@@ -76,19 +76,19 @@ namespace Umbraco.Core.ObjectResolution
/// <param name="httpContext">The HttpContextBase corresponding to the HttpRequest.</param>
/// <exception cref="ArgumentNullException"><paramref name="httpContext"/> is <c>null</c>.</exception>
protected ManyObjectsResolverBase(IServiceProvider serviceProvider, ILogger logger, HttpContextBase httpContext)
{
{
if (serviceProvider == null) throw new ArgumentNullException("serviceProvider");
if (httpContext == null) throw new ArgumentNullException("httpContext");
CanResolveBeforeFrozen = false;
Logger = logger;
LifetimeScope = ObjectLifetimeScope.HttpRequest;
_httpContextKey = GetType().FullName;
LifetimeScope = ObjectLifetimeScope.HttpRequest;
_httpContextKey = GetType().FullName;
ServiceProvider = serviceProvider;
CurrentHttpContext = httpContext;
_instanceTypes = new List<Type>();
_instanceTypes = new List<Type>();
InitializeAppInstances();
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use ctor specifying IServiceProvider instead")]
@@ -110,57 +110,57 @@ namespace Umbraco.Core.ObjectResolution
/// <exception cref="InvalidOperationException"><paramref name="scope"/> is per HttpRequest but the current HttpContext is null.</exception>
protected ManyObjectsResolverBase(IServiceProvider serviceProvider, ILogger logger, IEnumerable<Type> value, ObjectLifetimeScope scope = ObjectLifetimeScope.Application)
: this(serviceProvider, logger, scope)
{
_instanceTypes = value.ToList();
}
{
_instanceTypes = value.ToList();
}
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use ctor specifying IServiceProvider instead")]
protected ManyObjectsResolverBase(IEnumerable<Type> value, ObjectLifetimeScope scope = ObjectLifetimeScope.Application)
: this(new ActivatorServiceProvider(), LoggerResolver.Current.Logger, value, scope)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ManyObjectsResolverBase{TResolver, TResolved}"/> class with an initial list of objects,
/// with creation of objects based on an HttpRequest lifetime scope.
/// </summary>
/// <param name="httpContext">The HttpContextBase corresponding to the HttpRequest.</param>
/// <param name="value">The list of object types.</param>
/// <exception cref="ArgumentNullException"><paramref name="httpContext"/> is <c>null</c>.</exception>
/// <summary>
/// Initializes a new instance of the <see cref="ManyObjectsResolverBase{TResolver, TResolved}"/> class with an initial list of objects,
/// with creation of objects based on an HttpRequest lifetime scope.
/// </summary>
/// <param name="httpContext">The HttpContextBase corresponding to the HttpRequest.</param>
/// <param name="value">The list of object types.</param>
/// <exception cref="ArgumentNullException"><paramref name="httpContext"/> is <c>null</c>.</exception>
[Obsolete("Use ctor specifying IServiceProvider instead")]
protected ManyObjectsResolverBase(HttpContextBase httpContext, IEnumerable<Type> value)
: this(new ActivatorServiceProvider(), LoggerResolver.Current.Logger, httpContext)
{
_instanceTypes = value.ToList();
}
#endregion
{
_instanceTypes = value.ToList();
}
#endregion
private void InitializeAppInstances()
{
_applicationInstances = new Lazy<IEnumerable<TResolved>>(() => CreateInstances().ToArray());
}
/// <summary>
/// Gets or sets a value indicating whether the resolver can resolve objects before resolution is frozen.
/// </summary>
/// <remarks>This is false by default and is used for some special internal resolvers.</remarks>
internal bool CanResolveBeforeFrozen { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the resolver can resolve objects before resolution is frozen.
/// </summary>
/// <remarks>This is false by default and is used for some special internal resolvers.</remarks>
internal bool CanResolveBeforeFrozen { get; set; }
/// <summary>
/// Gets the list of types to create instances from.
/// </summary>
protected virtual IEnumerable<Type> InstanceTypes
{
get { return _instanceTypes; }
}
/// <summary>
/// Gets the list of types to create instances from.
/// </summary>
protected virtual IEnumerable<Type> InstanceTypes
{
get { return _instanceTypes; }
}
/// <summary>
/// Gets or sets the <see cref="HttpContextBase"/> used to initialize this object, if any.
/// </summary>
/// <remarks>If not null, then <c>LifetimeScope</c> will be <c>ObjectLifetimeScope.HttpRequest</c>.</remarks>
protected HttpContextBase CurrentHttpContext { get; private set; }
/// <summary>
/// Gets or sets the <see cref="HttpContextBase"/> used to initialize this object, if any.
/// </summary>
/// <remarks>If not null, then <c>LifetimeScope</c> will be <c>ObjectLifetimeScope.HttpRequest</c>.</remarks>
protected HttpContextBase CurrentHttpContext { get; private set; }
/// <summary>
/// Returns the service provider used to instantiate objects
@@ -174,16 +174,16 @@ namespace Umbraco.Core.ObjectResolution
/// </summary>
protected ObjectLifetimeScope LifetimeScope { get; private set; }
/// <summary>
/// Gets the resolved object instances, sorted by weight.
/// </summary>
/// <returns>The sorted resolved object instances.</returns>
/// <remarks>
/// <para>The order is based upon the <c>WeightedPluginAttribute</c> and <c>DefaultPluginWeight</c>.</para>
/// <para>Weights are sorted ascendingly (lowest weights come first).</para>
/// </remarks>
protected IEnumerable<TResolved> GetSortedValues()
{
/// <summary>
/// Gets the resolved object instances, sorted by weight.
/// </summary>
/// <returns>The sorted resolved object instances.</returns>
/// <remarks>
/// <para>The order is based upon the <c>WeightAttribute</c> and <c>DefaultPluginWeight</c>.</para>
/// <para>Weights are sorted ascendingly (lowest weights come first).</para>
/// </remarks>
protected IEnumerable<TResolved> GetSortedValues()
{
if (_sortedValues == null)
{
var values = Values.ToList();
@@ -191,18 +191,18 @@ namespace Umbraco.Core.ObjectResolution
_sortedValues = values;
}
return _sortedValues;
}
}
/// <summary>
/// Gets or sets the default type weight.
/// </summary>
/// <remarks>Determines the weight of types that do not have a <c>WeightedPluginAttribute</c> set on
/// them, when calling <c>GetSortedValues</c>.</remarks>
protected virtual int DefaultPluginWeight
{
get { return _defaultPluginWeight; }
set { _defaultPluginWeight = value; }
}
/// <summary>
/// Gets or sets the default type weight.
/// </summary>
/// <remarks>Determines the weight of types that do not have a <c>WeightAttribute</c> set on
/// them, when calling <c>GetSortedValues</c>.</remarks>
protected virtual int DefaultPluginWeight
{
get { return _defaultPluginWeight; }
set { _defaultPluginWeight = value; }
}
/// <summary>
/// Returns the weight of an object for user with GetSortedValues
@@ -210,22 +210,22 @@ namespace Umbraco.Core.ObjectResolution
/// <param name="o"></param>
/// <returns></returns>
protected virtual int GetObjectWeight(object o)
{
var type = o.GetType();
var attr = type.GetCustomAttribute<WeightedPluginAttribute>(true);
return attr == null ? DefaultPluginWeight : attr.Weight;
}
{
var type = o.GetType();
var attr = type.GetCustomAttribute<WeightAttribute>(true);
return attr == null ? DefaultPluginWeight : attr.Weight;
}
/// <summary>
/// Gets the resolved object instances.
/// </summary>
/// <exception cref="InvalidOperationException"><c>CanResolveBeforeFrozen</c> is false, and resolution is not frozen.</exception>
protected IEnumerable<TResolved> Values
{
get
{
using (Resolution.Reader(CanResolveBeforeFrozen))
{
/// <summary>
/// Gets the resolved object instances.
/// </summary>
/// <exception cref="InvalidOperationException"><c>CanResolveBeforeFrozen</c> is false, and resolution is not frozen.</exception>
protected IEnumerable<TResolved> Values
{
get
{
using (Resolution.Reader(CanResolveBeforeFrozen))
{
// note: we apply .ToArray() to the output of CreateInstance() because that is an IEnumerable that
// comes from the PluginManager we want to be _sure_ that it's not a Linq of some sort, but the
// instances have actually been instanciated when we return.
@@ -247,7 +247,7 @@ namespace Umbraco.Core.ObjectResolution
CurrentHttpContext.Items[_httpContextKey] = instances;
}
return (TResolved[])CurrentHttpContext.Items[_httpContextKey];
case ObjectLifetimeScope.Application:
return _applicationInstances.Value;
@@ -258,132 +258,132 @@ namespace Umbraco.Core.ObjectResolution
return CreateInstances().ToArray();
}
}
}
}
}
}
/// <summary>
/// Creates the object instances for the types contained in the types collection.
/// </summary>
/// <returns>A list of objects of type <typeparamref name="TResolved"/>.</returns>
protected virtual IEnumerable<TResolved> CreateInstances()
{
return ServiceProvider.CreateInstances<TResolved>(InstanceTypes, Logger);
}
/// <summary>
/// Creates the object instances for the types contained in the types collection.
/// </summary>
/// <returns>A list of objects of type <typeparamref name="TResolved"/>.</returns>
protected virtual IEnumerable<TResolved> CreateInstances()
{
return ServiceProvider.CreateInstances<TResolved>(InstanceTypes, Logger);
}
#region Types collection manipulation
#region Types collection manipulation
/// <summary>
/// Removes a type.
/// </summary>
/// <param name="value">The type to remove.</param>
/// <exception cref="InvalidOperationException">the resolver does not support removing types, or
/// the type is not a valid type for the resolver.</exception>
public virtual void RemoveType(Type value)
{
EnsureSupportsRemove();
/// <summary>
/// Removes a type.
/// </summary>
/// <param name="value">The type to remove.</param>
/// <exception cref="InvalidOperationException">the resolver does not support removing types, or
/// the type is not a valid type for the resolver.</exception>
public virtual void RemoveType(Type value)
{
EnsureSupportsRemove();
using (Resolution.Configuration)
using (var l = new UpgradeableReadLock(_lock))
{
EnsureCorrectType(value);
using (Resolution.Configuration)
using (var l = new UpgradeableReadLock(_lock))
{
EnsureCorrectType(value);
l.UpgradeToWriteLock();
_instanceTypes.Remove(value);
}
}
l.UpgradeToWriteLock();
_instanceTypes.Remove(value);
}
}
/// <summary>
/// Removes a type.
/// </summary>
/// <typeparam name="T">The type to remove.</typeparam>
/// <exception cref="InvalidOperationException">the resolver does not support removing types, or
/// the type is not a valid type for the resolver.</exception>
public void RemoveType<T>()
/// <summary>
/// Removes a type.
/// </summary>
/// <typeparam name="T">The type to remove.</typeparam>
/// <exception cref="InvalidOperationException">the resolver does not support removing types, or
/// the type is not a valid type for the resolver.</exception>
public void RemoveType<T>()
where T : TResolved
{
RemoveType(typeof(T));
}
{
RemoveType(typeof(T));
}
/// <summary>
/// Adds types.
/// </summary>
/// <param name="types">The types to add.</param>
/// <remarks>The types are appended at the end of the list.</remarks>
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
/// a type is not a valid type for the resolver, or a type is already in the collection of types.</exception>
protected void AddTypes(IEnumerable<Type> types)
{
EnsureSupportsAdd();
/// <summary>
/// Adds types.
/// </summary>
/// <param name="types">The types to add.</param>
/// <remarks>The types are appended at the end of the list.</remarks>
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
/// a type is not a valid type for the resolver, or a type is already in the collection of types.</exception>
protected void AddTypes(IEnumerable<Type> types)
{
EnsureSupportsAdd();
using (Resolution.Configuration)
using (new WriteLock(_lock))
{
foreach(var t in types)
{
EnsureCorrectType(t);
using (Resolution.Configuration)
using (new WriteLock(_lock))
{
foreach (var t in types)
{
EnsureCorrectType(t);
if (_instanceTypes.Contains(t))
{
throw new InvalidOperationException(string.Format(
"Type {0} is already in the collection of types.", t.FullName));
}
_instanceTypes.Add(t);
}
}
}
{
throw new InvalidOperationException(string.Format(
"Type {0} is already in the collection of types.", t.FullName));
}
_instanceTypes.Add(t);
}
}
}
/// <summary>
/// Adds a type.
/// </summary>
/// <param name="value">The type to add.</param>
/// <remarks>The type is appended at the end of the list.</remarks>
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
/// the type is not a valid type for the resolver, or the type is already in the collection of types.</exception>
public virtual void AddType(Type value)
{
EnsureSupportsAdd();
/// <summary>
/// Adds a type.
/// </summary>
/// <param name="value">The type to add.</param>
/// <remarks>The type is appended at the end of the list.</remarks>
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
/// the type is not a valid type for the resolver, or the type is already in the collection of types.</exception>
public virtual void AddType(Type value)
{
EnsureSupportsAdd();
using (Resolution.Configuration)
using (var l = new UpgradeableReadLock(_lock))
{
EnsureCorrectType(value);
using (Resolution.Configuration)
using (var l = new UpgradeableReadLock(_lock))
{
EnsureCorrectType(value);
if (_instanceTypes.Contains(value))
{
throw new InvalidOperationException(string.Format(
"Type {0} is already in the collection of types.", value.FullName));
}
{
throw new InvalidOperationException(string.Format(
"Type {0} is already in the collection of types.", value.FullName));
}
l.UpgradeToWriteLock();
_instanceTypes.Add(value);
}
}
l.UpgradeToWriteLock();
_instanceTypes.Add(value);
}
}
/// <summary>
/// Adds a type.
/// </summary>
/// <typeparam name="T">The type to add.</typeparam>
/// <remarks>The type is appended at the end of the list.</remarks>
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
/// the type is not a valid type for the resolver, or the type is already in the collection of types.</exception>
public void AddType<T>()
/// <summary>
/// Adds a type.
/// </summary>
/// <typeparam name="T">The type to add.</typeparam>
/// <remarks>The type is appended at the end of the list.</remarks>
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
/// the type is not a valid type for the resolver, or the type is already in the collection of types.</exception>
public void AddType<T>()
where T : TResolved
{
AddType(typeof(T));
}
{
AddType(typeof(T));
}
/// <summary>
/// Clears the list of types
/// </summary>
/// <exception cref="InvalidOperationException">the resolver does not support clearing types.</exception>
public virtual void Clear()
{
EnsureSupportsClear();
/// <summary>
/// Clears the list of types
/// </summary>
/// <exception cref="InvalidOperationException">the resolver does not support clearing types.</exception>
public virtual void Clear()
{
EnsureSupportsClear();
using (Resolution.Configuration)
using (new WriteLock(_lock))
{
_instanceTypes.Clear();
}
}
using (Resolution.Configuration)
using (new WriteLock(_lock))
{
_instanceTypes.Clear();
}
}
/// <summary>
/// WARNING! Do not use this unless you know what you are doing, clear all types registered and instances
@@ -399,32 +399,32 @@ namespace Umbraco.Core.ObjectResolution
}
}
/// <summary>
/// Inserts a type at the specified index.
/// </summary>
/// <param name="index">The zero-based index at which the type should be inserted.</param>
/// <param name="value">The type to insert.</param>
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
/// the type is not a valid type for the resolver, or the type is already in the collection of types.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
public virtual void InsertType(int index, Type value)
{
EnsureSupportsInsert();
/// <summary>
/// Inserts a type at the specified index.
/// </summary>
/// <param name="index">The zero-based index at which the type should be inserted.</param>
/// <param name="value">The type to insert.</param>
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
/// the type is not a valid type for the resolver, or the type is already in the collection of types.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
public virtual void InsertType(int index, Type value)
{
EnsureSupportsInsert();
using (Resolution.Configuration)
using (var l = new UpgradeableReadLock(_lock))
{
EnsureCorrectType(value);
using (Resolution.Configuration)
using (var l = new UpgradeableReadLock(_lock))
{
EnsureCorrectType(value);
if (_instanceTypes.Contains(value))
{
throw new InvalidOperationException(string.Format(
"Type {0} is already in the collection of types.", value.FullName));
}
{
throw new InvalidOperationException(string.Format(
"Type {0} is already in the collection of types.", value.FullName));
}
l.UpgradeToWriteLock();
_instanceTypes.Insert(index, value);
}
}
l.UpgradeToWriteLock();
_instanceTypes.Insert(index, value);
}
}
/// <summary>
/// Inserts a type at the beginning of the list.
@@ -445,9 +445,9 @@ namespace Umbraco.Core.ObjectResolution
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
public void InsertType<T>(int index)
where T : TResolved
{
InsertType(index, typeof(T));
}
{
InsertType(index, typeof(T));
}
/// <summary>
/// Inserts a type at the beginning of the list.
@@ -458,68 +458,68 @@ namespace Umbraco.Core.ObjectResolution
{
InsertType(0, typeof(T));
}
/// <summary>
/// Inserts a type before a specified, already existing type.
/// </summary>
/// <param name="existingType">The existing type before which to insert.</param>
/// <param name="value">The type to insert.</param>
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
/// one of the types is not a valid type for the resolver, or the existing type is not in the collection,
/// or the new type is already in the collection of types.</exception>
public virtual void InsertTypeBefore(Type existingType, Type value)
{
EnsureSupportsInsert();
using (Resolution.Configuration)
using (var l = new UpgradeableReadLock(_lock))
{
EnsureCorrectType(existingType);
EnsureCorrectType(value);
/// <summary>
/// Inserts a type before a specified, already existing type.
/// </summary>
/// <param name="existingType">The existing type before which to insert.</param>
/// <param name="value">The type to insert.</param>
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
/// one of the types is not a valid type for the resolver, or the existing type is not in the collection,
/// or the new type is already in the collection of types.</exception>
public virtual void InsertTypeBefore(Type existingType, Type value)
{
EnsureSupportsInsert();
using (Resolution.Configuration)
using (var l = new UpgradeableReadLock(_lock))
{
EnsureCorrectType(existingType);
EnsureCorrectType(value);
if (_instanceTypes.Contains(existingType) == false)
{
throw new InvalidOperationException(string.Format(
"Type {0} is not in the collection of types.", existingType.FullName));
}
{
throw new InvalidOperationException(string.Format(
"Type {0} is not in the collection of types.", existingType.FullName));
}
if (_instanceTypes.Contains(value))
{
throw new InvalidOperationException(string.Format(
"Type {0} is already in the collection of types.", value.FullName));
}
{
throw new InvalidOperationException(string.Format(
"Type {0} is already in the collection of types.", value.FullName));
}
int index = _instanceTypes.IndexOf(existingType);
l.UpgradeToWriteLock();
_instanceTypes.Insert(index, value);
}
}
l.UpgradeToWriteLock();
_instanceTypes.Insert(index, value);
}
}
/// <summary>
/// Inserts a type before a specified, already existing type.
/// </summary>
/// <typeparam name="TExisting">The existing type before which to insert.</typeparam>
/// <typeparam name="T">The type to insert.</typeparam>
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
/// one of the types is not a valid type for the resolver, or the existing type is not in the collection,
/// or the new type is already in the collection of types.</exception>
public void InsertTypeBefore<TExisting, T>()
/// <summary>
/// Inserts a type before a specified, already existing type.
/// </summary>
/// <typeparam name="TExisting">The existing type before which to insert.</typeparam>
/// <typeparam name="T">The type to insert.</typeparam>
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
/// one of the types is not a valid type for the resolver, or the existing type is not in the collection,
/// or the new type is already in the collection of types.</exception>
public void InsertTypeBefore<TExisting, T>()
where TExisting : TResolved
where T : TResolved
{
InsertTypeBefore(typeof(TExisting), typeof(T));
}
{
InsertTypeBefore(typeof(TExisting), typeof(T));
}
/// <summary>
/// Returns a value indicating whether the specified type is already in the collection of types.
/// </summary>
/// <param name="value">The type to look for.</param>
/// <returns>A value indicating whether the type is already in the collection of types.</returns>
public virtual bool ContainsType(Type value)
{
using (new ReadLock(_lock))
{
return _instanceTypes.Contains(value);
}
}
/// <summary>
/// Returns a value indicating whether the specified type is already in the collection of types.
/// </summary>
/// <param name="value">The type to look for.</param>
/// <returns>A value indicating whether the type is already in the collection of types.</returns>
public virtual bool ContainsType(Type value)
{
using (new ReadLock(_lock))
{
return _instanceTypes.Contains(value);
}
}
/// <summary>
/// Gets the types in the collection of types.
@@ -536,27 +536,27 @@ namespace Umbraco.Core.ObjectResolution
return types;
}
/// <summary>
/// Returns a value indicating whether the specified type is already in the collection of types.
/// </summary>
/// <typeparam name="T">The type to look for.</typeparam>
/// <returns>A value indicating whether the type is already in the collection of types.</returns>
public bool ContainsType<T>()
/// <summary>
/// Returns a value indicating whether the specified type is already in the collection of types.
/// </summary>
/// <typeparam name="T">The type to look for.</typeparam>
/// <returns>A value indicating whether the type is already in the collection of types.</returns>
public bool ContainsType<T>()
where T : TResolved
{
return ContainsType(typeof(T));
}
{
return ContainsType(typeof(T));
}
#endregion
#endregion
/// <summary>
/// Returns a WriteLock to use when modifying collections
/// </summary>
/// <returns></returns>
protected WriteLock GetWriteLock()
{
return new WriteLock(_lock);
}
/// <summary>
/// Returns a WriteLock to use when modifying collections
/// </summary>
/// <returns></returns>
protected WriteLock GetWriteLock()
{
return new WriteLock(_lock);
}
#region Type utilities
@@ -581,70 +581,71 @@ namespace Umbraco.Core.ObjectResolution
/// </summary>
/// <exception cref="InvalidOperationException">The resolver does not support removing types.</exception>
protected void EnsureSupportsRemove()
{
if (SupportsRemove == false)
{
if (SupportsRemove == false)
throw new InvalidOperationException("This resolver does not support removing types");
}
}
/// <summary>
/// Ensures that the resolver supports clearing types.
/// </summary>
/// <exception cref="InvalidOperationException">The resolver does not support clearing types.</exception>
protected void EnsureSupportsClear() {
if (SupportsClear == false)
protected void EnsureSupportsClear()
{
if (SupportsClear == false)
throw new InvalidOperationException("This resolver does not support clearing types");
}
}
/// <summary>
/// Ensures that the resolver supports adding types.
/// </summary>
/// <exception cref="InvalidOperationException">The resolver does not support adding types.</exception>
protected void EnsureSupportsAdd()
{
if (SupportsAdd == false)
{
if (SupportsAdd == false)
throw new InvalidOperationException("This resolver does not support adding new types");
}
}
/// <summary>
/// Ensures that the resolver supports inserting types.
/// </summary>
/// <exception cref="InvalidOperationException">The resolver does not support inserting types.</exception>
protected void EnsureSupportsInsert()
{
if (SupportsInsert == false)
{
if (SupportsInsert == false)
throw new InvalidOperationException("This resolver does not support inserting new types");
}
}
/// <summary>
/// Gets a value indicating whether the resolver supports adding types.
/// </summary>
protected virtual bool SupportsAdd
{
get { return true; }
}
{
get { return true; }
}
/// <summary>
/// Gets a value indicating whether the resolver supports inserting types.
/// </summary>
protected virtual bool SupportsInsert
{
get { return true; }
}
{
get { return true; }
}
/// <summary>
/// Gets a value indicating whether the resolver supports clearing types.
/// </summary>
protected virtual bool SupportsClear
{
get { return true; }
}
{
get { return true; }
}
/// <summary>
/// Gets a value indicating whether the resolver supports removing types.
/// </summary>
protected virtual bool SupportsRemove
{
get { return true; }
{
get { return true; }
}
#endregion
@@ -1,25 +1,25 @@
using System;
namespace Umbraco.Core.ObjectResolution
{
/// <summary>
/// Indicates the relative weight of a resolved object type.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
internal class WeightedPluginAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="WeightedPluginAttribute"/> class with a weight.
/// </summary>
/// <param name="weight">The object type weight.</param>
public WeightedPluginAttribute(int weight)
{
Weight = weight;
}
/// <summary>
/// Gets or sets the weight of the object type.
/// </summary>
public int Weight { get; private set; }
}
using System;
namespace Umbraco.Core.ObjectResolution
{
/// <summary>
/// Indicates the relative weight of a resolved object type.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class WeightAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="WeightAttribute"/> class with a weight.
/// </summary>
/// <param name="weight">The object type weight.</param>
public WeightAttribute(int weight)
{
Weight = weight;
}
/// <summary>
/// Gets or sets the weight of the object type.
/// </summary>
public int Weight { get; private set; }
}
}
File diff suppressed because it is too large Load Diff
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
{
internal static class DefinitionFactory
{
public static TableDefinition GetTableDefinition(Type modelType)
public static TableDefinition GetTableDefinition(ISqlSyntaxProvider syntaxProvider, Type modelType)
{
//Looks for PetaPoco's TableNameAtribute for the name of the table
//If no attribute is set we use the name of the Type as the default convention
@@ -32,7 +32,7 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
//Otherwise use the name of the property itself as the default convention
var columnAttribute = propertyInfo.FirstAttribute<ColumnAttribute>();
string columnName = columnAttribute != null ? columnAttribute.Name : propertyInfo.Name;
var columnDefinition = GetColumnDefinition(modelType, propertyInfo, columnName, tableName);
var columnDefinition = GetColumnDefinition(syntaxProvider, modelType, propertyInfo, columnName, tableName);
tableDefinition.Columns.Add(columnDefinition);
//Creates a foreignkey definition and adds it to the collection on the table definition
@@ -58,7 +58,7 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
return tableDefinition;
}
public static ColumnDefinition GetColumnDefinition(Type modelType, PropertyInfo propertyInfo, string columnName, string tableName)
public static ColumnDefinition GetColumnDefinition(ISqlSyntaxProvider syntaxProvider, Type modelType, PropertyInfo propertyInfo, string columnName, string tableName)
{
var definition = new ColumnDefinition{ Name = columnName, TableName = tableName, ModificationType = ModificationType.Create };
@@ -110,7 +110,7 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
{
//Special case for MySQL as it can't have multiple default DateTime values, which
//is what the umbracoServer table definition is trying to create
if (SqlSyntaxContext.SqlSyntaxProvider is MySqlSyntaxProvider && definition.TableName == "umbracoServer" &&
if (syntaxProvider is MySqlSyntaxProvider && definition.TableName == "umbracoServer" &&
definition.TableName.ToLowerInvariant() == "lastNotifiedDate".ToLowerInvariant())
return definition;
@@ -99,7 +99,7 @@ namespace Umbraco.Core.Persistence
public void CreateTable(bool overwrite, Type modelType)
{
var tableDefinition = DefinitionFactory.GetTableDefinition(modelType);
var tableDefinition = DefinitionFactory.GetTableDefinition(_syntaxProvider, modelType);
var tableName = tableDefinition.Name;
string createSql = _syntaxProvider.Format(tableDefinition);
@@ -4,9 +4,11 @@ using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data.SqlServerCe;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MySql.Data.MySqlClient;
using Umbraco.Core.Logging;
namespace Umbraco.Core.Persistence
@@ -69,6 +71,38 @@ namespace Umbraco.Core.Persistence
}
}
public static string GetConnStringExSecurityInfo(this IDbConnection connection)
{
try
{
if (connection is SqlConnection)
{
var builder = new SqlConnectionStringBuilder(connection.ConnectionString);
return string.Format("DataSource: {0}, InitialCatalog: {1}", builder.DataSource, builder.InitialCatalog);
}
if (connection is SqlCeConnection)
{
var builder = new SqlCeConnectionStringBuilder(connection.ConnectionString);
return string.Format("DataSource: {0}", builder.DataSource);
}
if (connection is MySqlConnection)
{
var builder = new MySqlConnectionStringBuilder(connection.ConnectionString);
return string.Format("Server: {0}, Database: {1}", builder.Server, builder.Database);
}
}
catch (Exception ex)
{
LogHelper.WarnWithException(typeof(DbConnectionExtensions),
"Could not resolve connection string parameters", ex);
return "(Could not resolve)";
}
throw new ArgumentException(string.Format("The connection type {0} is not supported", connection.GetType()));
}
public static bool IsAvailable(this IDbConnection connection)
{
try
@@ -79,7 +113,8 @@ namespace Umbraco.Core.Persistence
catch (DbException exc)
{
// Don't swallow this error, the exception is super handy for knowing "why" its not available
LogHelper.WarnWithException<IDbConnection>("Configured database is reporting as not being available!", exc);
LogHelper.WarnWithException(typeof(DbConnectionExtensions),
"Configured database is reporting as not being available! {0}", exc, connection.GetConnStringExSecurityInfo);
return false;
}
@@ -27,15 +27,28 @@ namespace Umbraco.Core.Persistence.Factories
#region Implementation of IEntityFactory<IContent,DocumentDto>
public IContent BuildEntity(DocumentDto dto)
/// <summary>
/// Builds a IContent item from the dto(s) and content type
/// </summary>
/// <param name="dto">
/// This DTO can contain all of the information to build an IContent item, however in cases where multiple entities are being built,
/// a separate <see cref="DocumentPublishedReadOnlyDto"/> publishedDto entity will be supplied in place of the <see cref="DocumentDto"/>'s own
/// ResultColumn DocumentPublishedReadOnlyDto
/// </param>
/// <param name="contentType"></param>
/// <param name="publishedDto">
/// When querying for multiple content items the main DTO will not contain the ResultColumn DocumentPublishedReadOnlyDto and a separate publishedDto instance will be supplied
/// </param>
/// <returns></returns>
public static IContent BuildEntity(DocumentDto dto, IContentType contentType, DocumentPublishedReadOnlyDto publishedDto = null)
{
var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, _contentType);
var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, contentType);
try
{
content.DisableChangeTracking();
content.Id = _id;
content.Id = dto.NodeId;
content.Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId;
content.Name = dto.Text;
content.NodeName = dto.ContentVersionDto.ContentDto.NodeDto.Text;
@@ -49,11 +62,16 @@ namespace Umbraco.Core.Persistence.Factories
content.Published = dto.Published;
content.CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate;
content.UpdateDate = dto.ContentVersionDto.VersionDate;
content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?) null;
content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?) null;
content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?)null;
content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?)null;
content.Version = dto.ContentVersionDto.VersionId;
content.PublishedState = dto.Published ? PublishedState.Published : PublishedState.Unpublished;
content.PublishedVersionGuid = dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId;
//Check if the publishedDto has been supplied, if not the use the dto's own DocumentPublishedReadOnlyDto value
content.PublishedVersionGuid = publishedDto == null
? (dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId)
: publishedDto.VersionId;
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
@@ -64,6 +82,13 @@ namespace Umbraco.Core.Persistence.Factories
{
content.EnableChangeTracking();
}
}
[Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")]
public IContent BuildEntity(DocumentDto dto)
{
return BuildEntity(dto, _contentType);
}
public DocumentDto BuildDto(IContent entity)
@@ -27,15 +27,15 @@ namespace Umbraco.Core.Persistence.Factories
#region Implementation of IEntityFactory<IMedia,ContentVersionDto>
public IMedia BuildEntity(ContentVersionDto dto)
public static IMedia BuildEntity(ContentVersionDto dto, IMediaType contentType)
{
var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, _contentType);
var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, contentType);
try
{
media.DisableChangeTracking();
media.Id = _id;
media.Id = dto.NodeId;
media.Key = dto.ContentDto.NodeDto.UniqueId;
media.Path = dto.ContentDto.NodeDto.Path;
media.CreatorId = dto.ContentDto.NodeDto.UserId.Value;
@@ -55,6 +55,13 @@ namespace Umbraco.Core.Persistence.Factories
{
media.EnableChangeTracking();
}
}
[Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")]
public IMedia BuildEntity(ContentVersionDto dto)
{
return BuildEntity(dto, _contentType);
}
public ContentVersionDto BuildDto(IMedia entity)
@@ -30,11 +30,11 @@ namespace Umbraco.Core.Persistence.Factories
_updateDate = updateDate;
}
public IEnumerable<Property> BuildEntity(PropertyDataDto[] dtos)
public static IEnumerable<Property> BuildEntity(IReadOnlyCollection<PropertyDataDto> dtos, PropertyType[] compositionTypeProperties, DateTime createDate, DateTime updateDate)
{
var properties = new List<Property>();
foreach (var propertyType in _compositionTypeProperties)
foreach (var propertyType in compositionTypeProperties)
{
var propertyDataDto = dtos.LastOrDefault(x => x.PropertyTypeId == propertyType.Id);
var property = propertyDataDto == null
@@ -47,8 +47,8 @@ namespace Umbraco.Core.Persistence.Factories
//on initial construction we don't want to have dirty properties tracked
property.DisableChangeTracking();
property.CreateDate = _createDate;
property.UpdateDate = _updateDate;
property.CreateDate = createDate;
property.UpdateDate = updateDate;
// http://issues.umbraco.org/issue/U4-1946
property.ResetDirtyProperties(false);
properties.Add(property);
@@ -57,12 +57,18 @@ namespace Umbraco.Core.Persistence.Factories
{
property.EnableChangeTracking();
}
}
return properties;
}
[Obsolete("Use the static method instead, there's no reason to allocate one of these classes everytime we want to map values")]
public IEnumerable<Property> BuildEntity(PropertyDataDto[] dtos)
{
return BuildEntity(dtos, _compositionTypeProperties, _createDate, _updateDate);
}
public IEnumerable<PropertyDataDto> BuildDto(IEnumerable<Property> properties)
{
var propertyDataDtos = new List<PropertyDataDto>();
@@ -8,7 +8,17 @@ namespace Umbraco.Core.Persistence.Mappers
{
public abstract class BaseMapper
{
private readonly ISqlSyntaxProvider _sqlSyntax;
protected BaseMapper() : this(SqlSyntaxContext.SqlSyntaxProvider)
{
}
protected BaseMapper(ISqlSyntaxProvider sqlSyntax)
{
_sqlSyntax = sqlSyntax;
}
internal abstract ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache { get; }
internal abstract void BuildMap();
@@ -58,8 +68,8 @@ namespace Umbraco.Core.Persistence.Mappers
string columnName = columnAttribute.Name;
string columnMap = string.Format("{0}.{1}",
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(tableName),
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(columnName));
_sqlSyntax.GetQuotedTableName(tableName),
_sqlSyntax.GetQuotedColumnName(columnName));
return columnMap;
}
}
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Linq.Expressions;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -16,6 +17,11 @@ namespace Umbraco.Core.Persistence.Mappers
{
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance = new ConcurrentDictionary<string, DtoMapModel>();
public ContentMapper(ISqlSyntaxProvider sqlSyntax) : base(sqlSyntax)
{
}
//NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it
// otherwise that would fail because there is no public constructor.
public ContentMapper()
@@ -43,7 +43,7 @@ namespace Umbraco.Core.Persistence.Mappers
{
return byAttribute.Result;
}
throw new Exception("Invalid Type: A Mapper could not be resolved based on the passed in Type");
throw new Exception("Invalid Type: A Mapper could not be resolved based on the passed in Type " + type);
});
}
@@ -149,8 +149,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
private void CreateCmsContentTypeData()
{
_database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true });
_database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture" });
_database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document" });
_database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true });
_database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true });
_database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user" });
}
@@ -159,7 +159,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
foreach (var item in OrderedTables.OrderBy(x => x.Key))
{
var tableDefinition = DefinitionFactory.GetTableDefinition(item.Value);
var tableDefinition = DefinitionFactory.GetTableDefinition(_sqlSyntaxProvider, item.Value);
result.TableDefinitions.Add(tableDefinition);
}
@@ -0,0 +1,25 @@
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFive
{
/// <summary>
/// See: http://issues.umbraco.org/issue/U4-4196
/// </summary>
[Migration("7.5.5", 1, GlobalSettings.UmbracoMigrationName)]
public class UpdateAllowedMediaTypesAtRoot : MigrationBase
{
public UpdateAllowedMediaTypesAtRoot(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{ }
public override void Up()
{
Execute.Sql("UPDATE cmsContentType SET allowAtRoot = 1 WHERE nodeId = 1032 OR nodeId = 1033");
}
public override void Down()
{ }
}
}
@@ -0,0 +1,38 @@
using System.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero
{
/// <summary>
/// See: http://issues.umbraco.org/issue/U4-9188
/// </summary>
[Migration("7.6.0", 0, GlobalSettings.UmbracoMigrationName)]
public class UpdateUniqueIndexOnCmsPropertyData : MigrationBase
{
public UpdateUniqueIndexOnCmsPropertyData(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{
}
public override void Up()
{
//tuple = tablename, indexname, columnname, unique
var indexes = SqlSyntax.GetDefinedIndexes(Context.Database).ToArray();
var found = indexes.FirstOrDefault(
x => x.Item1.InvariantEquals("cmsPropertyData")
&& x.Item2.InvariantEquals("IX_cmsPropertyData"));
if (found != null)
{
//drop the index
Delete.Index("IX_cmsPropertyData").OnTable("cmsPropertyData");
}
}
public override void Down()
{
}
}
}
+16 -3
View File
@@ -739,6 +739,11 @@ namespace Umbraco.Core.Persistence
/// <param name="take"></param>
internal virtual void BuildSqlDbSpecificPagingQuery(DBType databaseType, long skip, long take, string sql, string sqlSelectRemoved, string sqlOrderBy, ref object[] args, out string sqlPage)
{
// this is overriden in UmbracoDatabase, and if running SqlServer >=2012, the database type
// is switched from SqlServer to SqlServerCE in order to use the better paging syntax that
// SqlCE supports, and SqlServer >=2012 too.
// so the first case is actually for SqlServer <2012, and second case is CE *and* SqlServer >=2012
if (databaseType == DBType.SqlServer || databaseType == DBType.Oracle)
{
sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, "");
@@ -746,8 +751,16 @@ namespace Umbraco.Core.Persistence
{
sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner";
}
sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}",
sqlOrderBy == null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1);
// split to ensure that peta_rn is the last field to be selected, else Page<int> would fail
// the resulting sql is not perfect, NPoco has a much nicer way to do it, but it would require
// importing large parts of NPoco
var pos = sqlSelectRemoved.IndexOf("FROM");
var sqlColumns = sqlSelectRemoved.Substring(0, pos);
var sqlFrom = sqlSelectRemoved.Substring(pos);
sqlPage = string.Format("SELECT * FROM (SELECT {0}, ROW_NUMBER() OVER ({1}) peta_rn {2}) peta_paged WHERE peta_rn>@{3} AND peta_rn<=@{4}",
sqlColumns, sqlOrderBy ?? "ORDER BY (SELECT NULL)", sqlFrom, args.Length, args.Length + 1);
args = args.Concat(new object[] { skip, skip + take }).ToArray();
}
else if (databaseType == DBType.SqlServerCE)
@@ -774,7 +787,7 @@ namespace Umbraco.Core.Persistence
throw new Exception("Unable to parse SQL statement for paged query");
if (_dbType == DBType.Oracle && sqlSelectRemoved.StartsWith("*"))
throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id");
BuildSqlDbSpecificPagingQuery(_dbType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage);
}
@@ -3,9 +3,13 @@ using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data.SqlServerCe;
using System.Linq;
using System.Text.RegularExpressions;
using MySql.Data.MySqlClient;
using StackExchange.Profiling.Data;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
@@ -56,7 +60,7 @@ namespace Umbraco.Core.Persistence
/// Safely inserts a record, or updates if it exists, based on a unique constraint.
/// </summary>
/// <param name="db"></param>
/// <param name="poco"></param>
/// <param name="poco"></param>
/// <returns>The action that executed, either an insert or an update. If an insert occurred and a PK value got generated, the poco object
/// passed in will contain the updated value.</returns>
/// <remarks>
@@ -78,7 +82,7 @@ namespace Umbraco.Core.Persistence
/// </summary>
/// <param name="db"></param>
/// <param name="poco"></param>
/// <param name="updateArgs"></param>
/// <param name="updateArgs"></param>
/// <param name="updateCommand">If the entity has a composite key they you need to specify the update command explicitly</param>
/// <returns>The action that executed, either an insert or an update. If an insert occurred and a PK value got generated, the poco object
/// passed in will contain the updated value.</returns>
@@ -130,7 +134,7 @@ namespace Umbraco.Core.Persistence
if (rowCount > 0)
return RecordPersistenceType.Update;
// failed: does not exist (due to race cond RC2), need to insert
// failed: does not exist (due to race cond RC2), need to insert
// loop
}
}
@@ -150,7 +154,7 @@ namespace Umbraco.Core.Persistence
{
//this fancy regex will only match a single @ not a double, etc...
var regex = new Regex("(?<!@)@(?!@)");
return regex.Replace(value, "@@");
return regex.Replace(value, "@@");
}
return value;
@@ -172,6 +176,13 @@ namespace Umbraco.Core.Persistence
creator.CreateTable<T>(overwrite);
}
/// <summary>
/// Performs the bulk insertion in the context of a current transaction with an optional parameter to complete the transaction
/// when finished
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="db"></param>
/// <param name="collection"></param>
public static void BulkInsertRecords<T>(this Database db, IEnumerable<T> collection)
{
//don't do anything if there are no records.
@@ -180,7 +191,7 @@ namespace Umbraco.Core.Persistence
using (var tr = db.GetTransaction())
{
db.BulkInsertRecords(collection, tr, true);
db.BulkInsertRecords(collection, tr, SqlSyntaxContext.SqlSyntaxProvider, true, true); // use native, commit
}
}
@@ -192,55 +203,98 @@ namespace Umbraco.Core.Persistence
/// <param name="db"></param>
/// <param name="collection"></param>
/// <param name="tr"></param>
/// <param name="syntaxProvider"></param>
/// <param name="useNativeSqlPlatformBulkInsert">
/// If this is false this will try to just generate bulk insert statements instead of using the current SQL platform's bulk
/// insert logic. For SQLCE, bulk insert statements do not work so if this is false it will insert one at a time.
/// </param>
/// <param name="commitTrans"></param>
public static void BulkInsertRecords<T>(this Database db, IEnumerable<T> collection, Transaction tr, bool commitTrans = false)
/// <returns>The number of items inserted</returns>
public static int BulkInsertRecords<T>(this Database db,
IEnumerable<T> collection,
Transaction tr,
ISqlSyntaxProvider syntaxProvider,
bool useNativeSqlPlatformBulkInsert = true,
bool commitTrans = false)
{
//don't do anything if there are no records.
if (collection.Any() == false)
return;
{
return 0;
}
var pd = Database.PocoData.ForType(typeof(T));
if (pd == null) throw new InvalidOperationException("Could not find PocoData for " + typeof(T));
try
{
//if it is sql ce or it is a sql server version less than 2008, we need to do individual inserts.
var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider;
int processed = 0;
if ((sqlServerSyntax != null && (int)sqlServerSyntax.GetVersionName(db) < (int)SqlServerVersionName.V2008)
|| SqlSyntaxContext.SqlSyntaxProvider is SqlCeSyntaxProvider)
{
//SqlCe doesn't support bulk insert statements!
var usedNativeSqlPlatformInserts = useNativeSqlPlatformBulkInsert
&& NativeSqlPlatformBulkInsertRecords(db, syntaxProvider, pd, collection, out processed);
foreach (var poco in collection)
{
db.Insert(poco);
}
}
else
if (usedNativeSqlPlatformInserts == false)
{
string[] sqlStatements;
var cmds = db.GenerateBulkInsertCommand(collection, db.Connection, out sqlStatements);
for (var i = 0; i < sqlStatements.Length; i++)
//if it is sql ce or it is a sql server version less than 2008, we need to do individual inserts.
var sqlServerSyntax = syntaxProvider as SqlServerSyntaxProvider;
if ((sqlServerSyntax != null && (int) sqlServerSyntax.GetVersionName(db) < (int) SqlServerVersionName.V2008)
|| syntaxProvider is SqlCeSyntaxProvider)
{
using (var cmd = cmds[i])
//SqlCe doesn't support bulk insert statements!
foreach (var poco in collection)
{
cmd.CommandText = sqlStatements[i];
cmd.ExecuteNonQuery();
db.Insert(poco);
}
}
else
{
//we'll need to generate insert statements instead
string[] sqlStatements;
var cmds = db.GenerateBulkInsertCommand(pd, collection, out sqlStatements);
for (var i = 0; i < sqlStatements.Length; i++)
{
using (var cmd = cmds[i])
{
cmd.CommandText = sqlStatements[i];
cmd.ExecuteNonQuery();
processed++;
}
}
}
}
if (commitTrans)
{
tr.Complete();
tr.Complete();
}
return processed;
}
catch
{
if (commitTrans)
{
tr.Dispose();
tr.Dispose();
}
throw;
}
}
/// <summary>
/// Performs the bulk insertion in the context of a current transaction with an optional parameter to complete the transaction
/// when finished
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="db"></param>
/// <param name="collection"></param>
/// <param name="tr"></param>
/// <param name="commitTrans"></param>
[Obsolete("Use the method that specifies an SqlSyntaxContext instance instead")]
public static void BulkInsertRecords<T>(this Database db, IEnumerable<T> collection, Transaction tr, bool commitTrans = false)
{
db.BulkInsertRecords<T>(collection, tr, SqlSyntaxContext.SqlSyntaxProvider, commitTrans);
}
/// <summary>
@@ -249,45 +303,36 @@ namespace Umbraco.Core.Persistence
/// <typeparam name="T"></typeparam>
/// <param name="db"></param>
/// <param name="collection"></param>
/// <param name="connection"></param>
/// <param name="sql"></param>
/// <param name="pd"></param>
/// <returns>Sql commands with populated command parameters required to execute the sql statement</returns>
/// <remarks>
/// The limits for number of parameters are 2100 (in sql server, I think there's many more allowed in mysql). So
/// we need to detect that many params and split somehow.
/// For some reason the 2100 limit is not actually allowed even though the exception from sql server mentions 2100 as a max, perhaps it is 2099
/// The limits for number of parameters are 2100 (in sql server, I think there's many more allowed in mysql). So
/// we need to detect that many params and split somehow.
/// For some reason the 2100 limit is not actually allowed even though the exception from sql server mentions 2100 as a max, perhaps it is 2099
/// that is max. I've reduced it to 2000 anyways.
/// </remarks>
internal static IDbCommand[] GenerateBulkInsertCommand<T>(
this Database db,
IEnumerable<T> collection,
IDbConnection connection,
this Database db,
Database.PocoData pd,
IEnumerable<T> collection,
out string[] sql)
{
//A filter used below a few times to get all columns except result cols and not the primary key if it is auto-incremental
Func<Database.PocoData, KeyValuePair<string, Database.PocoColumn>, bool> includeColumn = (data, column) =>
{
if (column.Value.ResultColumn) return false;
if (data.TableInfo.AutoIncrement && column.Key == data.TableInfo.PrimaryKey) return false;
return true;
};
var pd = Database.PocoData.ForType(typeof(T));
var tableName = db.EscapeTableName(pd.TableInfo.TableName);
//get all columns to include and format for sql
var cols = string.Join(", ",
var cols = string.Join(", ",
pd.Columns
.Where(c => includeColumn(pd, c))
.Where(c => IncludeColumn(pd, c))
.Select(c => tableName + "." + db.EscapeSqlIdentifier(c.Key)).ToArray());
var itemArray = collection.ToArray();
//calculate number of parameters per item
var paramsPerItem = pd.Columns.Count(i => includeColumn(pd, i));
var paramsPerItem = pd.Columns.Count(i => IncludeColumn(pd, i));
//Example calc:
// Given: we have 4168 items in the itemArray, each item contains 8 command parameters (values to be inserterted)
// Given: we have 4168 items in the itemArray, each item contains 8 command parameters (values to be inserterted)
// 2100 / 8 = 262.5
// Math.Floor(2100 / 8) = 262 items per trans
// 4168 / 262 = 15.908... = there will be 16 trans in total
@@ -306,14 +351,14 @@ namespace Umbraco.Core.Persistence
.Skip(tIndex * (int)itemsPerTrans)
.Take((int)itemsPerTrans);
var cmd = db.CreateCommand(connection, "");
var cmd = db.CreateCommand(db.Connection, string.Empty);
var pocoValues = new List<string>();
var index = 0;
foreach (var poco in itemsForTrans)
{
var values = new List<string>();
//get all columns except result cols and not the primary key if it is auto-incremental
foreach (var i in pd.Columns.Where(x => includeColumn(pd, x)))
foreach (var i in pd.Columns.Where(x => IncludeColumn(pd, x)))
{
db.AddParam(cmd, i.Value.GetValue(poco), "@");
values.Add(string.Format("{0}{1}", "@", index++));
@@ -321,14 +366,211 @@ namespace Umbraco.Core.Persistence
pocoValues.Add("(" + string.Join(",", values.ToArray()) + ")");
}
var sqlResult = string.Format("INSERT INTO {0} ({1}) VALUES {2}", tableName, cols, string.Join(", ", pocoValues));
var sqlResult = string.Format("INSERT INTO {0} ({1}) VALUES {2}", tableName, cols, string.Join(", ", pocoValues));
sqlQueries.Add(sqlResult);
commands.Add(cmd);
}
sql = sqlQueries.ToArray();
return commands.ToArray();
return commands.ToArray();
}
/// <summary>
/// A filter used below a few times to get all columns except result cols and not the primary key if it is auto-incremental
/// </summary>
/// <param name="data"></param>
/// <param name="column"></param>
/// <returns></returns>
private static bool IncludeColumn(Database.PocoData data, KeyValuePair<string, Database.PocoColumn> column)
{
if (column.Value.ResultColumn) return false;
if (data.TableInfo.AutoIncrement && column.Key == data.TableInfo.PrimaryKey) return false;
return true;
}
/// <summary>
/// Bulk insert records with Sql BulkCopy or TableDirect or whatever sql platform specific bulk insert records should be used
/// </summary>
/// <param name="db"></param>
/// <param name="syntaxProvider"></param>
/// <param name="pd"></param>
/// <param name="collection"></param>
/// <param name="processed">The number of records inserted</param>
private static bool NativeSqlPlatformBulkInsertRecords<T>(Database db, ISqlSyntaxProvider syntaxProvider, Database.PocoData pd, IEnumerable<T> collection, out int processed)
{
var dbConnection = db.Connection;
//unwrap the profiled connection if there is one
var profiledConnection = dbConnection as ProfiledDbConnection;
if (profiledConnection != null)
{
dbConnection = profiledConnection.InnerConnection;
}
//check if it's SQL or SqlCe
var sqlConnection = dbConnection as SqlConnection;
if (sqlConnection != null)
{
processed = BulkInsertRecordsSqlServer(db, (SqlServerSyntaxProvider)syntaxProvider, pd, collection);
return true;
}
var sqlCeConnection = dbConnection as SqlCeConnection;
if (sqlCeConnection != null)
{
processed = BulkInsertRecordsSqlCe(db, pd, collection);
return true;
}
//could not use the SQL server's specific bulk insert operations
processed = 0;
return false;
}
/// <summary>
/// Logic used to perform bulk inserts with SqlCe's TableDirect
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="db"></param>
/// <param name="pd"></param>
/// <param name="collection"></param>
/// <returns></returns>
internal static int BulkInsertRecordsSqlCe<T>(Database db,
Database.PocoData pd,
IEnumerable<T> collection)
{
var cols = pd.Columns.ToArray();
using (var cmd = db.CreateCommand(db.Connection, string.Empty))
{
cmd.CommandText = pd.TableInfo.TableName;
cmd.CommandType = CommandType.TableDirect;
//cmd.Transaction = GetTypedTransaction<SqlCeTransaction>(db.Connection.);
//get the real command
using (var sqlCeCommand = GetTypedCommand<SqlCeCommand>(cmd))
{
// This seems to cause problems, I think this is primarily used for retrieval, not
// inserting. see: https://msdn.microsoft.com/en-us/library/system.data.sqlserverce.sqlcecommand.indexname%28v=vs.100%29.aspx?f=255&MSPPError=-2147217396
//sqlCeCommand.IndexName = pd.TableInfo.PrimaryKey;
var count = 0;
using (var rs = sqlCeCommand.ExecuteResultSet(ResultSetOptions.Updatable))
{
var rec = rs.CreateRecord();
foreach (var item in collection)
{
for (var i = 0; i < cols.Length; i++)
{
//skip the index if this shouldn't be included (i.e. PK)
if (IncludeColumn(pd, cols[i]))
{
var val = cols[i].Value.GetValue(item);
rec.SetValue(i, val);
}
}
rs.Insert(rec);
count++;
}
}
return count;
}
}
}
/// <summary>
/// Logic used to perform bulk inserts with SqlServer's BulkCopy
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="db"></param>
/// <param name="sqlSyntaxProvider"></param>
/// <param name="pd"></param>
/// <param name="collection"></param>
/// <returns></returns>
internal static int BulkInsertRecordsSqlServer<T>(Database db, SqlServerSyntaxProvider sqlSyntaxProvider,
Database.PocoData pd, IEnumerable<T> collection)
{
//NOTE: We need to use the original db.Connection here to create the command, but we need to pass in the typed
// connection below to the SqlBulkCopy
using (var cmd = db.CreateCommand(db.Connection, string.Empty))
{
using (var copy = new SqlBulkCopy(
GetTypedConnection<SqlConnection>(db.Connection),
SqlBulkCopyOptions.Default,
GetTypedTransaction<SqlTransaction>(cmd.Transaction))
{
BulkCopyTimeout = 10000,
DestinationTableName = pd.TableInfo.TableName
})
{
//var cols = pd.Columns.Where(x => IncludeColumn(pd, x)).Select(x => x.Value).ToArray();
using (var bulkReader = new PocoDataDataReader<T, SqlServerSyntaxProvider>(collection, pd, sqlSyntaxProvider))
{
copy.WriteToServer(bulkReader);
return bulkReader.RecordsAffected;
}
}
}
}
/// <summary>
/// Returns the underlying connection as a typed connection - this is used to unwrap the profiled mini profiler stuff
/// </summary>
/// <typeparam name="TConnection"></typeparam>
/// <param name="connection"></param>
/// <returns></returns>
private static TConnection GetTypedConnection<TConnection>(IDbConnection connection)
where TConnection : class, IDbConnection
{
var profiled = connection as ProfiledDbConnection;
if (profiled != null)
{
return profiled.InnerConnection as TConnection;
}
return connection as TConnection;
}
/// <summary>
/// Returns the underlying connection as a typed connection - this is used to unwrap the profiled mini profiler stuff
/// </summary>
/// <typeparam name="TTransaction"></typeparam>
/// <param name="connection"></param>
/// <returns></returns>
private static TTransaction GetTypedTransaction<TTransaction>(IDbTransaction connection)
where TTransaction : class, IDbTransaction
{
var profiled = connection as ProfiledDbTransaction;
if (profiled != null)
{
return profiled.WrappedTransaction as TTransaction;
}
return connection as TTransaction;
}
/// <summary>
/// Returns the underlying connection as a typed connection - this is used to unwrap the profiled mini profiler stuff
/// </summary>
/// <typeparam name="TCommand"></typeparam>
/// <param name="command"></param>
/// <returns></returns>
private static TCommand GetTypedCommand<TCommand>(IDbCommand command)
where TCommand : class, IDbCommand
{
var profiled = command as ProfiledDbCommand;
if (profiled != null)
{
return profiled.InternalCommand as TCommand;
}
return command as TCommand;
}
[Obsolete("Use the DatabaseSchemaHelper instead")]
@@ -405,8 +647,8 @@ namespace Umbraco.Core.Persistence
return ApplicationContext.Current.DatabaseContext.DatabaseProvider;
}
}
}
@@ -27,9 +27,17 @@ namespace Umbraco.Core.Persistence
return sql.From(sqlSyntax.GetQuotedTableName(tableName));
}
[Obsolete("Use the overload specifying ISqlSyntaxProvider instead")]
public static Sql Where<T>(this Sql sql, Expression<Func<T, bool>> predicate)
{
var expresionist = new PocoToSqlExpressionHelper<T>();
var expresionist = new PocoToSqlExpressionVisitor<T>();
var whereExpression = expresionist.Visit(predicate);
return sql.Where(whereExpression, expresionist.GetSqlParameters());
}
public static Sql Where<T>(this Sql sql, Expression<Func<T, bool>> predicate, ISqlSyntaxProvider sqlSyntax)
{
var expresionist = new PocoToSqlExpressionVisitor<T>(sqlSyntax);
var whereExpression = expresionist.Visit(predicate);
return sql.Where(whereExpression, expresionist.GetSqlParameters());
}
@@ -0,0 +1,159 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence
{
/// <summary>
/// A data reader used for reading collections of PocoData entity types
/// </summary>
/// <remarks>
/// We are using a custom data reader so that tons of memory is not consumed when rebuilding this table, previously
/// we'd generate SQL insert statements, but we'd have to put all of the XML structures into memory first. Alternatively
/// we can use .net's DataTable, but this also requires putting everything into memory. By using a DataReader we don't have to
/// store every content item and it's XML structure in memory to get it into the DB, we can stream it into the db with this
/// reader.
/// </remarks>
internal class PocoDataDataReader<T, TSyntax> : BulkDataReader
where TSyntax : ISqlSyntaxProvider
{
private readonly MicrosoftSqlSyntaxProviderBase<TSyntax> _sqlSyntaxProvider;
private readonly TableDefinition _tableDefinition;
private readonly Database.PocoColumn[] _readerColumns;
private readonly IEnumerator<T> _enumerator;
private readonly ColumnDefinition[] _columnDefinitions;
private int _recordsAffected = -1;
public PocoDataDataReader(
IEnumerable<T> dataSource,
Database.PocoData pd,
MicrosoftSqlSyntaxProviderBase<TSyntax> sqlSyntaxProvider)
{
if (dataSource == null) throw new ArgumentNullException("dataSource");
if (sqlSyntaxProvider == null) throw new ArgumentNullException("sqlSyntaxProvider");
_tableDefinition = DefinitionFactory.GetTableDefinition(sqlSyntaxProvider, pd.type);
if (_tableDefinition == null) throw new InvalidOperationException("No table definition found for type " + pd.type);
_readerColumns = pd.Columns.Select(x => x.Value).ToArray();
_sqlSyntaxProvider = sqlSyntaxProvider;
_enumerator = dataSource.GetEnumerator();
_columnDefinitions = _tableDefinition.Columns.ToArray();
}
protected override string SchemaName
{
get { return _tableDefinition.SchemaName; }
}
protected override string TableName
{
get { return _tableDefinition.Name; }
}
public override int RecordsAffected
{
get { return _recordsAffected <= 0 ? -1 : _recordsAffected; }
}
/// <summary>
/// This will automatically add the schema rows based on the Poco table definition and the columns passed in
/// </summary>
protected override void AddSchemaTableRows()
{
//var colNames = _readerColumns.Select(x => x.ColumnName).ToArray();
//foreach (var col in _columnDefinitions.Where(x => colNames.Contains(x.Name, StringComparer.OrdinalIgnoreCase)))
foreach (var col in _columnDefinitions)
{
var sqlDbType = SqlDbType.NVarChar;
if (col.HasSpecialDbType)
{
//get the SqlDbType from the 'special type'
switch (col.DbType)
{
case SpecialDbTypes.NTEXT:
sqlDbType = SqlDbType.NText;
break;
case SpecialDbTypes.NCHAR:
sqlDbType = SqlDbType.NChar;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
else if (col.Type.HasValue)
{
//get the SqlDbType from the DbType
sqlDbType = _sqlSyntaxProvider.GetSqlDbType(col.Type.Value);
}
else
{
//get the SqlDbType from the clr type
sqlDbType = _sqlSyntaxProvider.GetSqlDbType(col.PropertyType);
}
AddSchemaTableRow(
col.Name,
col.Size > 0 ? (int?)col.Size : null,
col.Precision > 0 ? (short?)col.Precision : null,
null, col.IsUnique, col.IsIdentity, col.IsNullable, sqlDbType,
null, null, null, null, null);
}
}
/// <summary>
/// Get the value from the column index for the current object
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
public override object GetValue(int i)
{
if (_enumerator.Current != null)
{
return _readerColumns[i].GetValue(_enumerator.Current);
//return _columnDefinitions[i]. .GetValue(_enumerator.Current);
}
return null;
//TODO: Or throw ?
}
/// <summary>
/// Advance the cursor
/// </summary>
/// <returns></returns>
public override bool Read()
{
var result = _enumerator.MoveNext();
if (result)
{
if (_recordsAffected == -1)
{
_recordsAffected = 0;
}
_recordsAffected++;
}
return result;
}
/// <summary>
/// Ensure the enumerator is disposed
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_enumerator.Dispose();
}
}
}
}
@@ -1,31 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace Umbraco.Core.Persistence.Querying
{
/// <summary>
/// SD: This is a horrible hack but unless we break compatibility with anyone who's actually implemented IQuery{T} there's not much we can do.
/// The IQuery{T} interface is useless without having a GetWhereClauses method and cannot be used for tests.
/// We have to wait till v8 to make this change I suppose.
/// </summary>
internal static class QueryExtensions
{
/// <summary>
/// Returns all translated where clauses and their sql parameters
/// </summary>
/// <returns></returns>
public static IEnumerable<Tuple<string, object[]>> GetWhereClauses<T>(this IQuery<T> query)
{
var q = query as Query<T>;
if (q == null)
{
throw new NotSupportedException(typeof(IQuery<T>) + " cannot be cast to " + typeof(Query<T>));
}
return q.GetWhereClauses();
}
}
/// <summary>
/// Represents a query for building Linq translatable SQL queries
/// </summary>
@@ -1,62 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
internal class ModelToSqlExpressionHelper<T> : BaseExpressionHelper<T>
{
private readonly BaseMapper _mapper;
public ModelToSqlExpressionHelper()
{
_mapper = MappingResolver.Current.ResolveMapperByType(typeof(T));
}
protected override string VisitMemberAccess(MemberExpression m)
{
if (m.Expression != null &&
m.Expression.NodeType == ExpressionType.Parameter
&& m.Expression.Type == typeof(T))
{
var field = _mapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
return field;
}
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
{
var field = _mapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
return field;
}
var member = Expression.Convert(m, typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
object o = getter();
SqlParameters.Add(o);
return string.Format("@{0}", SqlParameters.Count - 1);
//return GetQuotedValue(o, o != null ? o.GetType() : null);
}
//protected bool IsFieldName(string quotedExp)
//{
// //Not entirely sure this is reliable, but its better then simply returning true
// return quotedExp.LastIndexOf("'", StringComparison.InvariantCultureIgnoreCase) + 1 != quotedExp.Length;
//}
}
}
@@ -0,0 +1,75 @@
using System;
using System.Linq.Expressions;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
/// <summary>
/// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression,
/// based on Umbraco's business logic models.
/// </summary>
/// <remarks>This object is stateful and cannot be re-used to parse an expression.</remarks>
internal class ModelToSqlExpressionVisitor<T> : ExpressionVisitorBase
{
private readonly BaseMapper _mapper;
public ModelToSqlExpressionVisitor(ISqlSyntaxProvider sqlSyntax, BaseMapper mapper)
: base(sqlSyntax)
{
_mapper = mapper;
}
[Obsolete("Use the overload the specifies a SqlSyntaxProvider")]
public ModelToSqlExpressionVisitor()
: this(SqlSyntaxContext.SqlSyntaxProvider, MappingResolver.Current.ResolveMapperByType(typeof(T)))
{ }
protected override string VisitMemberAccess(MemberExpression m)
{
if (m.Expression != null &&
m.Expression.NodeType == ExpressionType.Parameter
&& m.Expression.Type == typeof(T))
{
//don't execute if compiled
if (Visited == false)
{
var field = _mapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
return field;
}
//already compiled, return
return string.Empty;
}
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
{
//don't execute if compiled
if (Visited == false)
{
var field = _mapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
return field;
}
//already compiled, return
return string.Empty;
}
var member = Expression.Convert(m, typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
var o = getter();
SqlParameters.Add(o);
//don't execute if compiled
if (Visited == false)
return string.Format("@{0}", SqlParameters.Count - 1);
//already compiled, return
return string.Empty;
}
}
}
@@ -1,62 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
internal class PocoToSqlExpressionHelper<T> : BaseExpressionHelper<T>
{
private readonly Database.PocoData _pd;
public PocoToSqlExpressionHelper()
{
_pd = new Database.PocoData(typeof(T));
}
protected override string VisitMemberAccess(MemberExpression m)
{
if (m.Expression != null &&
m.Expression.NodeType == ExpressionType.Parameter
&& m.Expression.Type == typeof(T))
{
string field = GetFieldName(_pd, m.Member.Name);
return field;
}
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
{
string field = GetFieldName(_pd, m.Member.Name);
return field;
}
var member = Expression.Convert(m, typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
object o = getter();
SqlParameters.Add(o);
return string.Format("@{0}", SqlParameters.Count - 1);
//return GetQuotedValue(o, o != null ? o.GetType() : null);
}
protected virtual string GetFieldName(Database.PocoData pocoData, string name)
{
var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name);
return string.Format("{0}.{1}",
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(pocoData.TableInfo.TableName),
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(column.Value.ColumnName));
}
//protected bool IsFieldName(string quotedExp)
//{
// return true;
//}
}
}
@@ -0,0 +1,80 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
/// <summary>
/// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression,
/// based on Umbraco's DTOs.
/// </summary>
/// <remarks>This object is stateful and cannot be re-used to parse an expression.</remarks>
internal class PocoToSqlExpressionVisitor<T> : ExpressionVisitorBase
{
private readonly Database.PocoData _pd;
public PocoToSqlExpressionVisitor(ISqlSyntaxProvider syntaxProvider)
: base(syntaxProvider)
{
_pd = new Database.PocoData(typeof(T));
}
[Obsolete("Use the overload the specifies a SqlSyntaxProvider")]
public PocoToSqlExpressionVisitor()
: this(SqlSyntaxContext.SqlSyntaxProvider)
{
}
protected override string VisitMemberAccess(MemberExpression m)
{
if (m.Expression != null &&
m.Expression.NodeType == ExpressionType.Parameter
&& m.Expression.Type == typeof(T))
{
//don't execute if compiled
if (Visited == false)
{
string field = GetFieldName(_pd, m.Member.Name);
return field;
}
//already compiled, return
return string.Empty;
}
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
{
//don't execute if compiled
if (Visited == false)
{
string field = GetFieldName(_pd, m.Member.Name);
return field;
}
//already compiled, return
return string.Empty;
}
var member = Expression.Convert(m, typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
var o = getter();
SqlParameters.Add(o);
//don't execute if compiled
if (Visited == false)
return string.Format("@{0}", SqlParameters.Count - 1);
//already compiled, return
return string.Empty;
}
protected virtual string GetFieldName(Database.PocoData pocoData, string name)
{
var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name);
return string.Format("{0}.{1}",
SqlSyntax.GetQuotedTableName(pocoData.TableInfo.TableName),
SqlSyntax.GetQuotedColumnName(column.Value.ColumnName));
}
}
}
@@ -30,7 +30,8 @@ namespace Umbraco.Core.Persistence.Querying
{
if (predicate != null)
{
var expressionHelper = new ModelToSqlExpressionHelper<T>();
//TODO: This should have an SqlSyntax object passed in, this ctor is relying on a singleton
var expressionHelper = new ModelToSqlExpressionVisitor<T>();
string whereExpression = expressionHelper.Visit(predicate);
_wheres.Add(new Tuple<string, object[]>(whereExpression, expressionHelper.GetSqlParameters()));
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Persistence.Querying
{
/// <summary>
/// SD: This is a horrible hack but unless we break compatibility with anyone who's actually implemented IQuery{T} there's not much we can do.
/// The IQuery{T} interface is useless without having a GetWhereClauses method and cannot be used for tests.
/// We have to wait till v8 to make this change I suppose.
/// </summary>
internal static class QueryExtensions
{
/// <summary>
/// Returns all translated where clauses and their sql parameters
/// </summary>
/// <returns></returns>
public static IEnumerable<Tuple<string, object[]>> GetWhereClauses<T>(this IQuery<T> query)
{
var q = query as Query<T>;
if (q == null)
{
throw new NotSupportedException(typeof(IQuery<T>) + " cannot be cast to " + typeof(Query<T>));
}
return q.GetWhereClauses();
}
}
}
@@ -0,0 +1,31 @@
namespace Umbraco.Core.Persistence.Repositories
{
internal enum BaseQueryType
{
/// <summary>
/// A query to return all information for a single item
/// </summary>
/// <remarks>
/// In some cases this will be the same as <see cref="FullMultiple"/>
/// </remarks>
FullSingle,
/// <summary>
/// A query to return all information for multiple items
/// </summary>
/// <remarks>
/// In some cases this will be the same as <see cref="FullSingle"/>
/// </remarks>
FullMultiple,
/// <summary>
/// A query to return the ids for items
/// </summary>
Ids,
/// <summary>
/// A query to return the count for items
/// </summary>
Count
}
}
@@ -1,22 +1,15 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http.Headers;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Dynamics;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
@@ -61,9 +54,9 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IContent PerformGet(int id)
{
var sql = GetBaseQuery(false)
var sql = GetBaseQuery(BaseQueryType.FullSingle)
.Where(GetBaseWhereClause(), new { Id = id })
.Where<DocumentDto>(x => x.Newest)
.Where<DocumentDto>(x => x.Newest, SqlSyntax)
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
var dto = Database.Fetch<DocumentDto, ContentVersionDto, ContentDto, NodeDto, DocumentPublishedReadOnlyDto>(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault();
@@ -78,63 +71,102 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IEnumerable<IContent> PerformGetAll(params int[] ids)
{
var sql = GetBaseQuery(false);
if (ids.Any())
Func<Sql, Sql> translate = s =>
{
sql.Where("umbracoNode.id in (@ids)", new { ids = ids });
}
if (ids.Any())
{
s.Where("umbracoNode.id in (@ids)", new { ids });
}
//we only want the newest ones with this method
s.Where<DocumentDto>(x => x.Newest, SqlSyntax);
return s;
};
var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple);
var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids);
//we only want the newest ones with this method
sql.Where<DocumentDto>(x => x.Newest);
return ProcessQuery(sql);
return ProcessQuery(translate(sqlBaseFull), new PagingSqlQuery(translate(sqlBaseIds)));
}
protected override IEnumerable<IContent> PerformGetByQuery(IQuery<IContent> query)
{
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator<IContent>(sqlClause, query);
var sql = translator.Translate()
.Where<DocumentDto>(x => x.Newest)
.OrderByDescending<ContentVersionDto>(x => x.VersionDate)
.OrderBy<NodeDto>(x => x.SortOrder);
var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple);
var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids);
return ProcessQuery(sql);
Func<SqlTranslator<IContent>, Sql> translate = (translator) =>
{
return translator.Translate()
.Where<DocumentDto>(x => x.Newest, SqlSyntax)
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax)
.OrderBy<NodeDto>(x => x.SortOrder, SqlSyntax);
};
var translatorFull = new SqlTranslator<IContent>(sqlBaseFull, query);
var translatorIds = new SqlTranslator<IContent>(sqlBaseIds, query);
return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)));
}
#endregion
#endregion
#region Overrides of PetaPocoRepositoryBase<IContent>
protected override Sql GetBaseQuery(bool isCount)
/// <summary>
/// Returns the base query to return Content
/// </summary>
/// <param name="queryType"></param>
/// <returns></returns>
/// <remarks>
/// Content queries will differ depending on what needs to be returned:
/// * FullSingle: When querying for a single document, this will include the Outer join to fetch the content item's published version info
/// * FullMultiple: When querying for multiple documents, this will exclude the Outer join to fetch the content item's published version info - this info would need to be fetched separately
/// * Ids: This would essentially be the same as FullMultiple however the columns specified will only return the Ids for the documents
/// * Count: A query to return the count for documents
/// </remarks>
protected override Sql GetBaseQuery(BaseQueryType queryType)
{
var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)",
var sql = new Sql();
sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsDocument.nodeId" : "*"))
.From<DocumentDto>(SqlSyntax)
.InnerJoin<ContentVersionDto>(SqlSyntax)
.On<DocumentDto, ContentVersionDto>(SqlSyntax, left => left.VersionId, right => right.VersionId)
.InnerJoin<ContentDto>(SqlSyntax)
.On<ContentVersionDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
.InnerJoin<NodeDto>(SqlSyntax)
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId);
if (queryType == BaseQueryType.FullSingle)
{
//The only reason we apply this left outer join is to be able to pull back the DocumentPublishedReadOnlyDto
//information with the entire data set, so basically this will get both the latest document and also it's published
//version if it has one. When performing a count or when retrieving Ids like in paging, this is unecessary
//and causes huge performance overhead for the SQL server, especially when sorting the result.
//We also don't include this outer join when querying for multiple entities since it is much faster to fetch this information
//in a separate query. For a single entity this is ok.
var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)",
SqlSyntax.GetQuotedTableName("cmsDocument"),
SqlSyntax.GetQuotedTableName("cmsDocument2"),
SqlSyntax.GetQuotedColumnName("nodeId"),
SqlSyntax.GetQuotedColumnName("published"));
var sql = new Sql();
sql.Select(isCount ? "COUNT(*)" : "*")
.From<DocumentDto>()
.InnerJoin<ContentVersionDto>()
.On<DocumentDto, ContentVersionDto>(left => left.VersionId, right => right.VersionId)
.InnerJoin<ContentDto>()
.On<ContentVersionDto, ContentDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<NodeDto>()
.On<ContentDto, NodeDto>(left => left.NodeId, right => right.NodeId)
// cannot do this because PetaPoco does not know how to alias the table
//.LeftOuterJoin<DocumentPublishedReadOnlyDto>()
//.On<DocumentDto, DocumentPublishedReadOnlyDto>(left => left.NodeId, right => right.NodeId)
// so have to rely on writing our own SQL
.Append(sqlx/*, new { @published = true }*/)
sql.Append(sqlx /*, new { @published = true }*/);
}
sql.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
return sql;
}
protected override Sql GetBaseQuery(bool isCount)
{
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle);
}
protected override string GetBaseWhereClause()
{
return "umbracoNode.id = @Id";
@@ -173,97 +205,82 @@ namespace Umbraco.Core.Persistence.Repositories
#region Overrides of VersionableRepositoryBase<IContent>
public void RebuildXmlStructures(Func<IContent, XElement> serializer, int groupSize = 5000, IEnumerable<int> contentTypeIds = null)
public void RebuildXmlStructures(Func<IContent, XElement> serializer, int groupSize = 200, IEnumerable<int> contentTypeIds = null)
{
// the previous way of doing this was to run it all in one big transaction,
// and to bulk-insert groups of xml rows - which works, until the transaction
// times out - and besides, because v7 transactions are ReadCommited, it does
// not bring much safety - so this reverts to updating each record individually,
// and it may be slower in the end, but should be more resilient.
//Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too.
using (var tr = Database.GetTransaction())
var contentTypeIdsA = contentTypeIds == null ? new int[0] : contentTypeIds.ToArray();
Func<int, Sql, Sql> translate = (bId, sql) =>
{
//Remove all the data first, if anything fails after this it's no problem the transaction will be reverted
if (contentTypeIds == null)
if (contentTypeIdsA.Length > 0)
{
var subQuery = new Sql()
.Select("DISTINCT cmsContentXml.nodeId")
.From<ContentXmlDto>()
.InnerJoin<DocumentDto>()
.On<ContentXmlDto, DocumentDto>(left => left.NodeId, right => right.NodeId);
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
Database.Execute(deleteSql);
sql.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
}
else
{
foreach (var id in contentTypeIds)
{
var id1 = id;
var subQuery = new Sql()
.Select("cmsDocument.nodeId")
.From<DocumentDto>()
.InnerJoin<ContentDto>()
.On<DocumentDto, ContentDto>(left => left.NodeId, right => right.NodeId)
.Where<DocumentDto>(dto => dto.Published)
.Where<ContentDto>(dto => dto.ContentTypeId == id1);
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
Database.Execute(deleteSql);
sql
.Where<NodeDto>(x => x.NodeId > bId && x.Trashed == false, SqlSyntax)
.Where<DocumentDto>(x => x.Published, SqlSyntax)
.OrderBy<NodeDto>(x => x.NodeId, SqlSyntax);
return sql;
};
var baseId = 0;
while (true)
{
// get the next group of nodes
var sqlFull = translate(baseId, GetBaseQuery(BaseQueryType.FullMultiple));
var sqlIds = translate(baseId, GetBaseQuery(BaseQueryType.Ids));
var xmlItems = ProcessQuery(SqlSyntax.SelectTop(sqlFull, groupSize), new PagingSqlQuery(SqlSyntax.SelectTop(sqlIds, groupSize)))
.Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() })
.ToList();
// no more nodes, break
if (xmlItems.Count == 0) break;
foreach (var xmlItem in xmlItems)
{
try
{
// should happen in most cases, then it tries to insert, and it should work
// unless the node has been deleted, and we just report the exception
Database.InsertOrUpdate(xmlItem);
}
catch (Exception e)
{
Logger.Error<MediaRepository>("Could not rebuild XML for nodeId=" + xmlItem.NodeId, e);
}
}
//now insert the data, again if something fails here, the whole transaction is reversed
if (contentTypeIds == null)
{
var query = Query<IContent>.Builder.Where(x => x.Published == true);
RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize);
}
else
{
foreach (var contentTypeId in contentTypeIds)
{
//copy local
var id = contentTypeId;
var query = Query<IContent>.Builder.Where(x => x.Published == true && x.ContentTypeId == id && x.Trashed == false);
RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize);
}
}
tr.Complete();
baseId = xmlItems[xmlItems.Count - 1].NodeId;
}
}
private void RebuildXmlStructuresProcessQuery(Func<IContent, XElement> serializer, IQuery<IContent> query, Transaction tr, int pageSize)
public override IEnumerable<IContent> GetAllVersions(int id)
{
var pageIndex = 0;
var total = long.MinValue;
var processed = 0;
do
Func<Sql, Sql> translate = s =>
{
//NOTE: This is an important call, we cannot simply make a call to:
// GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending);
// because that method is used to query 'latest' content items where in this case we don't necessarily
// want latest content items because a pulished content item might not actually be the latest.
// see: http://issues.umbraco.org/issue/U4-6322 & http://issues.umbraco.org/issue/U4-5982
var descendants = GetPagedResultsByQuery<DocumentDto, Content>(query, pageIndex, pageSize, out total,
new Tuple<string, string>("cmsDocument", "nodeId"),
ProcessQuery, "Path", Direction.Ascending, true);
return s.Where(GetBaseWhereClause(), new {Id = id})
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
};
var xmlItems = (from descendant in descendants
let xml = serializer(descendant)
select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray();
//bulk insert it into the database
Database.BulkInsertRecords(xmlItems, tr);
processed += xmlItems.Length;
pageIndex++;
} while (processed < total);
var sqlFull = translate(GetBaseQuery(BaseQueryType.FullMultiple));
var sqlIds = translate(GetBaseQuery(BaseQueryType.Ids));
return ProcessQuery(sqlFull, new PagingSqlQuery(sqlIds), true);
}
public override IContent GetByVersion(Guid versionId)
{
var sql = GetBaseQuery(false);
var sql = GetBaseQuery(BaseQueryType.FullSingle);
sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId });
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate);
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
var dto = Database.Fetch<DocumentDto, ContentVersionDto, ContentDto, NodeDto, DocumentPublishedReadOnlyDto>(sql).FirstOrDefault();
@@ -279,10 +296,10 @@ namespace Umbraco.Core.Persistence.Repositories
{
var sql = new Sql()
.Select("*")
.From<DocumentDto>()
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, DocumentDto>(left => left.VersionId, right => right.VersionId)
.Where<ContentVersionDto>(x => x.VersionId == versionId)
.Where<DocumentDto>(x => x.Newest != true);
.From<DocumentDto>(SqlSyntax)
.InnerJoin<ContentVersionDto>(SqlSyntax).On<ContentVersionDto, DocumentDto>(SqlSyntax, left => left.VersionId, right => right.VersionId)
.Where<ContentVersionDto>(x => x.VersionId == versionId, SqlSyntax)
.Where<DocumentDto>(x => x.Newest != true, SqlSyntax);
var dto = Database.Fetch<DocumentDto, ContentVersionDto>(sql).FirstOrDefault();
if (dto == null) return;
@@ -383,6 +400,7 @@ namespace Umbraco.Core.Persistence.Repositories
//Update with new correct path
nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId);
nodeDto.ValidatePathWithException();
Database.Update(nodeDto);
//Update entity with correct values
@@ -520,6 +538,7 @@ namespace Umbraco.Core.Persistence.Repositories
//Updates the (base) node data - umbracoNode
var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto;
nodeDto.ValidatePathWithException();
var o = Database.Update(nodeDto);
//Only update this DTO if the contentType has actually changed
@@ -655,41 +674,27 @@ namespace Umbraco.Core.Persistence.Repositories
public IEnumerable<IContent> GetByPublishedVersion(IQuery<IContent> query)
{
Func<SqlTranslator<IContent>, Sql> translate = t =>
{
return t.Translate()
.Where<DocumentDto>(x => x.Published, SqlSyntax)
.OrderBy<NodeDto>(x => x.Level, SqlSyntax)
.OrderBy<NodeDto>(x => x.SortOrder, SqlSyntax);
};
// we WANT to return contents in top-down order, ie parents should come before children
// ideal would be pure xml "document order" which can be achieved with:
// ORDER BY substring(path, 1, len(path) - charindex(',', reverse(path))), sortOrder
// but that's probably an overkill - sorting by level,sortOrder should be enough
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator<IContent>(sqlClause, query);
var sql = translator.Translate()
.Where<DocumentDto>(x => x.Published)
.OrderBy<NodeDto>(x => x.Level, SqlSyntax)
.OrderBy<NodeDto>(x => x.SortOrder, SqlSyntax);
var sqlFull = GetBaseQuery(BaseQueryType.FullMultiple);
var translatorFull = new SqlTranslator<IContent>(sqlFull, query);
var sqlIds = GetBaseQuery(BaseQueryType.Ids);
var translatorIds = new SqlTranslator<IContent>(sqlIds, query);
//NOTE: This doesn't allow properties to be part of the query
var dtos = Database.Fetch<DocumentDto, ContentVersionDto, ContentDto, NodeDto, DocumentPublishedReadOnlyDto>(sql);
foreach (var dto in dtos)
{
//Check in the cache first. If it exists there AND it is published
// then we can use that entity. Otherwise if it is not published (which can be the case
// because we only store the 'latest' entries in the cache which might not be the published
// version)
var fromCache = RuntimeCache.GetCacheItem<IContent>(GetCacheIdKey<IContent>(dto.NodeId));
//var fromCache = TryGetFromCache(dto.NodeId);
if (fromCache != null && fromCache.Published)
{
yield return fromCache;
}
else
{
yield return CreateContentFromDto(dto, dto.VersionId, sql);
}
}
return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)), true);
}
/// <summary>
/// This builds the Xml document used for the XML cache
/// </summary>
@@ -721,10 +726,14 @@ namespace Umbraco.Core.Persistence.Repositories
parent.Attributes.Append(pIdAtt);
xmlDoc.AppendChild(parent);
const string sql = @"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.xml, umbracoNode.level from umbracoNode
//Ensure that only nodes that have published versions are selected
var sql = string.Format(@"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.{0}, umbracoNode.{1} from umbracoNode
inner join cmsContentXml on cmsContentXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type
where umbracoNode.id in (select cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1)
order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
SqlSyntax.GetQuotedColumnName("xml"),
SqlSyntax.GetQuotedColumnName("level"),
SqlSyntax.GetQuotedColumnName("level"));
XmlElement last = null;
@@ -775,13 +784,8 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
public void ClearPublished(IContent content)
{
// race cond!
var documentDtos = Database.Fetch<DocumentDto>("WHERE nodeId=@id AND published=@published", new { id = content.Id, published = true });
foreach (var documentDto in documentDtos)
{
documentDto.Published = false;
Database.Update(documentDto);
}
var sql = "UPDATE cmsDocument SET published=0 WHERE nodeId=@id AND published=1";
Database.Execute(sql, new {id = content.Id});
}
/// <summary>
@@ -861,9 +865,9 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
Func<Tuple<string, object[]>> filterCallback = () => new Tuple<string, object[]>(filterSql.SQL, filterSql.Arguments);
return GetPagedResultsByQuery<DocumentDto, Content>(query, pageIndex, pageSize, out totalRecords,
return GetPagedResultsByQuery<DocumentDto>(query, pageIndex, pageSize, out totalRecords,
new Tuple<string, string>("cmsDocument", "nodeId"),
ProcessQuery, orderBy, orderDirection, orderBySystemField,
(sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
@@ -893,84 +897,144 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
return base.GetDatabaseFieldNameForOrderBy(orderBy);
}
private IEnumerable<IContent> ProcessQuery(Sql sql)
{
//NOTE: This doesn't allow properties to be part of the query
var dtos = Database.Fetch<DocumentDto, ContentVersionDto, ContentDto, NodeDto, DocumentPublishedReadOnlyDto>(sql);
//nothing found
if (dtos.Any() == false) return Enumerable.Empty<IContent>();
//content types
//NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types
var contentTypes = _contentTypeRepository.GetAll(dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray())
.ToArray();
var ids = dtos
.Where(dto => dto.TemplateId.HasValue && dto.TemplateId.Value > 0)
.Select(x => x.TemplateId.Value).ToArray();
//NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types
var templates = ids.Length == 0 ? Enumerable.Empty<ITemplate>() : _templateRepository.GetAll(ids).ToArray();
var dtosWithContentTypes = dtos
//This select into and null check are required because we don't have a foreign damn key on the contentType column
// http://issues.umbraco.org/issue/U4-5503
.Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) })
.Where(x => x.contentType != null)
.ToArray();
//Go get the property data for each document
var docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition(
d.dto.NodeId,
d.dto.VersionId,
d.dto.ContentVersionDto.VersionDate,
d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate,
d.contentType));
var propertyData = GetPropertyCollection(sql, docDefs);
return dtosWithContentTypes.Select(d => CreateContentFromDto(
d.dto,
contentTypes.First(ct => ct.Id == d.dto.ContentVersionDto.ContentDto.ContentTypeId),
templates.FirstOrDefault(tem => tem.Id == (d.dto.TemplateId.HasValue ? d.dto.TemplateId.Value : -1)),
propertyData[d.dto.NodeId]));
}
/// <summary>
/// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion.
/// This is the underlying method that processes most queries for this repository
/// </summary>
/// <param name="dto"></param>
/// <param name="contentType"></param>
/// <param name="template"></param>
/// <param name="propCollection"></param>
/// <param name="sqlFull">
/// The FullMultiple SQL without the outer join to return all data required to create an IContent excluding it's published state data which this will query separately
/// </param>
/// <param name="pagingSqlQuery">
/// The Id SQL without the outer join to just return all document ids - used to process the properties for the content item
/// </param>
/// <param name="withCache"></param>
/// <returns></returns>
private IContent CreateContentFromDto(DocumentDto dto,
IContentType contentType,
ITemplate template,
Models.PropertyCollection propCollection)
private IEnumerable<IContent> ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false)
{
var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId);
var content = factory.BuildEntity(dto);
// fetch returns a list so it's ok to iterate it in this method
var dtos = Database.Fetch<DocumentDto, ContentVersionDto, ContentDto, NodeDto>(sqlFull);
if (dtos.Count == 0) return Enumerable.Empty<IContent>();
//Go and get all of the published version data separately for this data, this is because when we are querying
//for multiple content items we don't include the outer join to fetch this data in the same query because
//it is insanely slow. Instead we just fetch the published version data separately in one query.
//Check if template id is set on DocumentDto, and get ITemplate if it is.
if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0)
//we need to parse the original SQL statement and reduce the columns to just cmsDocument.nodeId so that we can use
// the statement to go get the published data for all of the items by using an inner join
var parsedOriginalSql = "SELECT cmsDocument.nodeId " + sqlFull.SQL.Substring(sqlFull.SQL.IndexOf("FROM", StringComparison.Ordinal));
//now remove everything from an Orderby clause and beyond
if (parsedOriginalSql.InvariantContains("ORDER BY "))
{
content.Template = template ?? _templateRepository.Get(dto.TemplateId.Value);
}
else
parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal));
}
var publishedSql = new Sql(@"SELECT *
FROM cmsDocument AS doc2
INNER JOIN
(" + parsedOriginalSql + @") as docData
ON doc2.nodeId = docData.nodeId
WHERE doc2.published = 1
ORDER BY doc2.nodeId
", sqlFull.Arguments);
//go and get the published version data, we do a Query here and not a Fetch so we are
//not allocating a whole list to memory just to allocate another list in memory since
//we are assigning this data to a keyed collection for fast lookup below
var publishedData = Database.Query<DocumentPublishedReadOnlyDto>(publishedSql);
var publishedDataCollection = new DocumentPublishedReadOnlyDtoCollection();
foreach (var publishedDto in publishedData)
{
//ensure there isn't one set.
content.Template = null;
//double check that there's no corrupt db data, there should only be a single published item
if (publishedDataCollection.Contains(publishedDto.NodeId) == false)
publishedDataCollection.Add(publishedDto);
}
content.Properties = propCollection;
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((Entity)content).ResetDirtyProperties(false);
var content = new IContent[dtos.Count];
var defs = new List<DocumentDefinition>();
var templateIds = new List<int>();
//track the looked up content types, even though the content types are cached
// they still need to be deep cloned out of the cache and we don't want to add
// the overhead of deep cloning them on every item in this loop
var contentTypes = new Dictionary<int, IContentType>();
for (var i = 0; i < dtos.Count; i++)
{
var dto = dtos[i];
DocumentPublishedReadOnlyDto publishedDto;
publishedDataCollection.TryGetValue(dto.NodeId, out publishedDto);
// if the cache contains the published version, use it
if (withCache)
{
var cached = RuntimeCache.GetCacheItem<IContent>(GetCacheIdKey<IContent>(dto.NodeId));
//only use this cached version if the dto returned is also the publish version, they must match
if (cached != null && cached.Published && dto.Published)
{
content[i] = cached;
continue;
}
}
// else, need to fetch from the database
// content type repository is full-cache so OK to get each one independently
IContentType contentType;
if (contentTypes.ContainsKey(dto.ContentVersionDto.ContentDto.ContentTypeId))
{
contentType = contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId];
}
else
{
contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId] = contentType;
}
content[i] = ContentFactory.BuildEntity(dto, contentType, publishedDto);
// need template
if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0)
templateIds.Add(dto.TemplateId.Value);
// need properties
defs.Add(new DocumentDefinition(
dto.NodeId,
dto.VersionId,
dto.ContentVersionDto.VersionDate,
dto.ContentVersionDto.ContentDto.NodeDto.CreateDate,
contentType
));
}
// load all required templates in 1 query
var templates = _templateRepository.GetAll(templateIds.ToArray())
.ToDictionary(x => x.Id, x => x);
// load all properties for all documents from database in 1 query
var propertyData = GetPropertyCollection(pagingSqlQuery, defs);
// assign
var dtoIndex = 0;
foreach (var def in defs)
{
// move to corresponding item (which has to exist)
while (dtos[dtoIndex].NodeId != def.Id) dtoIndex++;
// complete the item
var cc = content[dtoIndex];
var dto = dtos[dtoIndex];
ITemplate template = null;
if (dto.TemplateId.HasValue)
templates.TryGetValue(dto.TemplateId.Value, out template); // else null
cc.Template = template;
cc.Properties = propertyData[cc.Id];
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
cc.ResetDirtyProperties(false);
}
return content;
}
@@ -985,8 +1049,7 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
{
var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId);
var content = factory.BuildEntity(dto);
var content = ContentFactory.BuildEntity(dto, contentType);
//Check if template id is set on DocumentDto, and get ITemplate if it is.
if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0)
@@ -1037,7 +1100,7 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
return currentName;
}
/// <summary>
/// Dispose disposable properties
/// </summary>
@@ -1052,5 +1115,26 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
_contentPreviewRepository.Dispose();
_contentXmlRepository.Dispose();
}
/// <summary>
/// A keyed collection for fast lookup when retrieving a separate list of published version data
/// </summary>
private class DocumentPublishedReadOnlyDtoCollection : KeyedCollection<int, DocumentPublishedReadOnlyDto>
{
protected override int GetKeyForItem(DocumentPublishedReadOnlyDto item)
{
return item.NodeId;
}
public bool TryGetValue(int key, out DocumentPublishedReadOnlyDto val)
{
if (Dictionary == null)
{
val = null;
return false;
}
return Dictionary.TryGetValue(key, out val);
}
}
}
}
@@ -62,17 +62,27 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IEnumerable<EntityContainer> PerformGetAll(params int[] ids)
{
//we need to batch these in groups of 2000 so we don't exceed the max 2100 limit
return ids.InGroupsOf(2000).SelectMany(@group =>
if (ids.Any())
{
//we need to batch these in groups of 2000 so we don't exceed the max 2100 limit
return ids.InGroupsOf(2000).SelectMany(@group =>
{
var sql = GetBaseQuery(false)
.Where("nodeObjectType=@umbracoObjectTypeId", new {umbracoObjectTypeId = NodeObjectTypeId})
.Where(string.Format("{0} IN (@ids)", SqlSyntax.GetQuotedColumnName("id")), new {ids = @group});
sql.OrderBy<NodeDto>(x => x.Level, SqlSyntax);
return Database.Fetch<NodeDto>(sql).Select(CreateEntity);
});
}
else
{
var sql = GetBaseQuery(false)
.Where("nodeObjectType=@umbracoObjectTypeId", new { umbracoObjectTypeId = NodeObjectTypeId })
.Where(string.Format("{0} IN (@ids)", SqlSyntax.GetQuotedColumnName("id")), new { ids = @group });
.Where("nodeObjectType=@umbracoObjectTypeId", new {umbracoObjectTypeId = NodeObjectTypeId});
sql.OrderBy<NodeDto>(x => x.Level, SqlSyntax);
return Database.Fetch<NodeDto>(sql).Select(CreateEntity);
});
}
}
protected override IEnumerable<EntityContainer> PerformGetByQuery(IQuery<EntityContainer> query)
@@ -96,5 +96,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
IEnumerable<IContent> GetPagedResultsByQuery(IQuery<IContent> query, long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection, bool orderBySystemField, IQuery<IContent> filter = null);
}
}
@@ -38,5 +38,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// <returns>An Enumerable list of <see cref="IMedia"/> objects</returns>
IEnumerable<IMedia> GetPagedResultsByQuery(IQuery<IMedia> query, long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "");
}
}
@@ -73,6 +73,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="content"></param>
/// <param name="xml"></param>
void AddOrUpdatePreviewXml(IMember content, Func<IMember, XElement> xml);
}
}
@@ -67,5 +67,16 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="id">Id of the <see cref="TEntity"/> object to delete versions from</param>
/// <param name="versionDate">Latest version date</param>
void DeleteVersions(int id, DateTime versionDate);
/// <summary>
/// Gets paged content descendants as XML by path
/// </summary>
/// <param name="path">Path starts with</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="orderBy"></param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of content items</returns>
IEnumerable<XElement> GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, string[] orderBy, out long totalRecords);
}
}
@@ -4,21 +4,17 @@ using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Dynamics;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Cache;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Services;
namespace Umbraco.Core.Persistence.Repositories
{
@@ -72,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories
sql.Where("umbracoNode.id in (@ids)", new { ids = ids });
}
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
protected override IEnumerable<IMedia> PerformGetByQuery(IQuery<IMedia> query)
@@ -80,26 +76,31 @@ namespace Umbraco.Core.Persistence.Repositories
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator<IMedia>(sqlClause, query);
var sql = translator.Translate()
.OrderBy<NodeDto>(x => x.SortOrder);
.OrderBy<NodeDto>(x => x.SortOrder, SqlSyntax);
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
#endregion
#region Overrides of PetaPocoRepositoryBase<int,IMedia>
protected override Sql GetBaseQuery(BaseQueryType queryType)
{
var sql = new Sql();
sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsContentVersion.contentId" : "*"))
.From<ContentVersionDto>(SqlSyntax)
.InnerJoin<ContentDto>(SqlSyntax)
.On<ContentVersionDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
.InnerJoin<NodeDto>(SqlSyntax)
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId, SqlSyntax)
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
return sql;
}
protected override Sql GetBaseQuery(bool isCount)
{
var sql = new Sql();
sql.Select(isCount ? "COUNT(*)" : "*")
.From<ContentVersionDto>()
.InnerJoin<ContentDto>()
.On<ContentVersionDto, ContentDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<NodeDto>()
.On<ContentDto, NodeDto>(left => left.NodeId, right => right.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
return sql;
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle);
}
protected override string GetBaseWhereClause()
@@ -137,116 +138,162 @@ namespace Umbraco.Core.Persistence.Repositories
#region Overrides of VersionableRepositoryBase<IContent>
public override IEnumerable<IMedia> GetAllVersions(int id)
{
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { Id = id })
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
return ProcessQuery(sql, new PagingSqlQuery(sql), true);
}
/// <summary>
/// This is the underlying method that processes most queries for this repository
/// </summary>
/// <param name="sqlFull">
/// The full SQL to select all media data
/// </param>
/// <param name="pagingSqlQuery">
/// The Id SQL to just return all media ids - used to process the properties for the media item
/// </param>
/// <param name="withCache"></param>
/// <returns></returns>
private IEnumerable<IMedia> ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false)
{
// fetch returns a list so it's ok to iterate it in this method
var dtos = Database.Fetch<ContentVersionDto, ContentDto, NodeDto>(sqlFull);
var content = new IMedia[dtos.Count];
var defs = new List<DocumentDefinition>();
//track the looked up content types, even though the content types are cached
// they still need to be deep cloned out of the cache and we don't want to add
// the overhead of deep cloning them on every item in this loop
var contentTypes = new Dictionary<int, IMediaType>();
for (var i = 0; i < dtos.Count; i++)
{
var dto = dtos[i];
// if the cache contains the item, use it
if (withCache)
{
var cached = RuntimeCache.GetCacheItem<IMedia>(GetCacheIdKey<IMedia>(dto.NodeId));
if (cached != null)
{
content[i] = cached;
continue;
}
}
// else, need to fetch from the database
// content type repository is full-cache so OK to get each one independently
IMediaType contentType;
if (contentTypes.ContainsKey(dto.ContentDto.ContentTypeId))
{
contentType = contentTypes[dto.ContentDto.ContentTypeId];
}
else
{
contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId);
contentTypes[dto.ContentDto.ContentTypeId] = contentType;
}
content[i] = MediaFactory.BuildEntity(dto, contentType);
// need properties
defs.Add(new DocumentDefinition(
dto.NodeId,
dto.VersionId,
dto.VersionDate,
dto.ContentDto.NodeDto.CreateDate,
contentType
));
}
// load all properties for all documents from database in 1 query
var propertyData = GetPropertyCollection(pagingSqlQuery, defs);
// assign
var dtoIndex = 0;
foreach (var def in defs)
{
// move to corresponding item (which has to exist)
while (dtos[dtoIndex].NodeId != def.Id) dtoIndex++;
// complete the item
var cc = content[dtoIndex];
cc.Properties = propertyData[cc.Id];
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
cc.ResetDirtyProperties(false);
}
return content;
}
public override IMedia GetByVersion(Guid versionId)
{
var sql = GetBaseQuery(false);
sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId });
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate);
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
var dto = Database.Fetch<ContentVersionDto, ContentDto, NodeDto>(sql).FirstOrDefault();
if (dto == null)
return null;
var mediaType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId);
var content = CreateMediaFromDto(dto, versionId, sql);
var factory = new MediaFactory(mediaType, NodeObjectTypeId, dto.NodeId);
var media = factory.BuildEntity(dto);
var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.VersionId, media.UpdateDate, media.CreateDate, mediaType) });
media.Properties = properties[dto.NodeId];
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((Entity)media).ResetDirtyProperties(false);
return media;
return content;
}
public void RebuildXmlStructures(Func<IMedia, XElement> serializer, int groupSize = 5000, IEnumerable<int> contentTypeIds = null)
public void RebuildXmlStructures(Func<IMedia, XElement> serializer, int groupSize = 200, IEnumerable<int> contentTypeIds = null)
{
// the previous way of doing this was to run it all in one big transaction,
// and to bulk-insert groups of xml rows - which works, until the transaction
// times out - and besides, because v7 transactions are ReadCommited, it does
// not bring much safety - so this reverts to updating each record individually,
// and it may be slower in the end, but should be more resilient.
//Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too.
using (var tr = Database.GetTransaction())
var baseId = 0;
var contentTypeIdsA = contentTypeIds == null ? new int[0] : contentTypeIds.ToArray();
while (true)
{
//Remove all the data first, if anything fails after this it's no problem the transaction will be reverted
if (contentTypeIds == null)
{
var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media);
var subQuery = new Sql()
.Select("DISTINCT cmsContentXml.nodeId")
.From<ContentXmlDto>()
.InnerJoin<NodeDto>()
.On<ContentXmlDto, NodeDto>(left => left.NodeId, right => right.NodeId)
.Where<NodeDto>(dto => dto.NodeObjectType == mediaObjectType);
// get the next group of nodes
var query = GetBaseQuery(false);
if (contentTypeIdsA.Length > 0)
query = query
.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
query = query
.Where<NodeDto>(x => x.NodeId > baseId, SqlSyntax)
.OrderBy<NodeDto>(x => x.NodeId, SqlSyntax);
var sql = SqlSyntax.SelectTop(query, groupSize);
var xmlItems = ProcessQuery(sql, new PagingSqlQuery(sql))
.Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() })
.ToList();
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
Database.Execute(deleteSql);
}
else
// no more nodes, break
if (xmlItems.Count == 0) break;
foreach (var xmlItem in xmlItems)
{
foreach (var id in contentTypeIds)
try
{
var id1 = id;
var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media);
var subQuery = new Sql()
.Select("DISTINCT cmsContentXml.nodeId")
.From<ContentXmlDto>()
.InnerJoin<NodeDto>()
.On<ContentXmlDto, NodeDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentDto>()
.On<ContentDto, NodeDto>(left => left.NodeId, right => right.NodeId)
.Where<NodeDto>(dto => dto.NodeObjectType == mediaObjectType)
.Where<ContentDto>(dto => dto.ContentTypeId == id1);
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
Database.Execute(deleteSql);
// InsertOrUpdate tries to update first, which is good since it is what
// should happen in most cases, then it tries to insert, and it should work
// unless the node has been deleted, and we just report the exception
Database.InsertOrUpdate(xmlItem);
}
catch (Exception e)
{
Logger.Error<MediaRepository>("Could not rebuild XML for nodeId=" + xmlItem.NodeId, e);
}
}
//now insert the data, again if something fails here, the whole transaction is reversed
if (contentTypeIds == null)
{
var query = Query<IMedia>.Builder;
RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize);
}
else
{
foreach (var contentTypeId in contentTypeIds)
{
//copy local
var id = contentTypeId;
var query = Query<IMedia>.Builder.Where(x => x.ContentTypeId == id && x.Trashed == false);
RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize);
}
}
tr.Complete();
baseId = xmlItems.Last().NodeId;
}
}
private void RebuildXmlStructuresProcessQuery(Func<IMedia, XElement> serializer, IQuery<IMedia> query, Transaction tr, int pageSize)
{
var pageIndex = 0;
var total = long.MinValue;
var processed = 0;
do
{
var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, true);
var xmlItems = (from descendant in descendants
let xml = serializer(descendant)
select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray();
//bulk insert it into the database
Database.BulkInsertRecords(xmlItems, tr);
processed += xmlItems.Length;
pageIndex++;
} while (processed < total);
}
public void AddOrUpdateContentXml(IMedia content, Func<IMedia, XElement> xml)
{
_contentXmlRepository.AddOrUpdate(new ContentXmlEntity<IMedia>(content, xml));
@@ -299,6 +346,7 @@ namespace Umbraco.Core.Persistence.Repositories
//Update with new correct path
nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId);
nodeDto.ValidatePathWithException();
Database.Update(nodeDto);
//Update entity with correct values
@@ -372,6 +420,7 @@ namespace Umbraco.Core.Persistence.Repositories
//Updates the (base) node data - umbracoNode
var nodeDto = dto.ContentDto.NodeDto;
nodeDto.ValidatePathWithException();
var o = Database.Update(nodeDto);
//Only update this DTO if the contentType has actually changed
@@ -468,86 +517,29 @@ namespace Umbraco.Core.Persistence.Repositories
filterCallback = () => new Tuple<string, object[]>(sbWhere.ToString().Trim(), args.ToArray());
}
return GetPagedResultsByQuery<ContentVersionDto, Models.Media>(query, pageIndex, pageSize, out totalRecords,
return GetPagedResultsByQuery<ContentVersionDto>(query, pageIndex, pageSize, out totalRecords,
new Tuple<string, string>("cmsContentVersion", "contentId"),
ProcessQuery, orderBy, orderDirection, orderBySystemField,
(sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
private IEnumerable<IMedia> ProcessQuery(Sql sql)
{
//NOTE: This doesn't allow properties to be part of the query
var dtos = Database.Fetch<ContentVersionDto, ContentDto, NodeDto>(sql);
var ids = dtos.Select(x => x.ContentDto.ContentTypeId).ToArray();
//content types
var contentTypes = ids.Length == 0 ? Enumerable.Empty<IMediaType>() : _mediaTypeRepository.GetAll(ids).ToArray();
var dtosWithContentTypes = dtos
//This select into and null check are required because we don't have a foreign damn key on the contentType column
// http://issues.umbraco.org/issue/U4-5503
.Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentDto.ContentTypeId) })
.Where(x => x.contentType != null)
.ToArray();
//Go get the property data for each document
var docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition(
d.dto.NodeId,
d.dto.VersionId,
d.dto.VersionDate,
d.dto.ContentDto.NodeDto.CreateDate,
d.contentType))
.ToArray();
var propertyData = GetPropertyCollection(sql, docDefs);
return dtosWithContentTypes.Select(d => CreateMediaFromDto(
d.dto,
contentTypes.First(ct => ct.Id == d.dto.ContentDto.ContentTypeId),
propertyData[d.dto.NodeId]));
}
/// <summary>
/// Private method to create a media object from a ContentDto
/// </summary>
/// <param name="d"></param>
/// <param name="contentType"></param>
/// <param name="propCollection"></param>
/// <returns></returns>
private IMedia CreateMediaFromDto(ContentVersionDto dto,
IMediaType contentType,
PropertyCollection propCollection)
{
var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId);
var media = factory.BuildEntity(dto);
media.Properties = propCollection;
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((Entity)media).ResetDirtyProperties(false);
return media;
}
/// <summary>
/// Private method to create a media object from a ContentDto
/// </summary>
/// <param name="d"></param>
/// <param name="dto"></param>
/// <param name="versionId"></param>
/// <param name="docSql"></param>
/// <returns></returns>
private IMedia CreateMediaFromDto(ContentVersionDto dto, Guid versionId, Sql docSql)
{
var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId);
var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId);
var media = factory.BuildEntity(dto);
var media = MediaFactory.BuildEntity(dto, contentType);
var docDef = new DocumentDefinition(dto.NodeId, versionId, media.UpdateDate, media.CreateDate, contentType);
var properties = GetPropertyCollection(docSql, new[] { docDef });
var properties = GetPropertyCollection(new PagingSqlQuery(docSql), new[] { docDef });
media.Properties = properties[dto.NodeId];
@@ -2,24 +2,19 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Cache;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Relators;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Dynamics;
namespace Umbraco.Core.Persistence.Repositories
{
@@ -73,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories
sql.Where("umbracoNode.id in (@ids)", new { ids = ids });
}
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
@@ -95,7 +90,7 @@ namespace Umbraco.Core.Persistence.Repositories
baseQuery.Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments))
.OrderBy<NodeDto>(x => x.SortOrder);
return ProcessQuery(baseQuery);
return ProcessQuery(baseQuery, new PagingSqlQuery(baseQuery));
}
else
{
@@ -103,7 +98,7 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = translator.Translate()
.OrderBy<NodeDto>(x => x.SortOrder);
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
}
@@ -112,24 +107,29 @@ namespace Umbraco.Core.Persistence.Repositories
#region Overrides of PetaPocoRepositoryBase<int,IMembershipUser>
protected override Sql GetBaseQuery(bool isCount)
protected override Sql GetBaseQuery(BaseQueryType queryType)
{
var sql = new Sql();
sql.Select(isCount ? "COUNT(*)" : "*")
.From<MemberDto>()
.InnerJoin<ContentVersionDto>()
.On<ContentVersionDto, MemberDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentDto>()
.On<ContentVersionDto, ContentDto>(left => left.NodeId, right => right.NodeId)
sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsMember.nodeId" : "*"))
.From<MemberDto>(SqlSyntax)
.InnerJoin<ContentVersionDto>(SqlSyntax)
.On<ContentVersionDto, MemberDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentDto>(SqlSyntax)
.On<ContentVersionDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
//We're joining the type so we can do a query against the member type - not sure if this adds much overhead or not?
// the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content
// types by default on the document and media repo's so we can query by content type there too.
.InnerJoin<ContentTypeDto>().On<ContentTypeDto, ContentDto>(left => left.NodeId, right => right.ContentTypeId)
.InnerJoin<NodeDto>()
.On<ContentDto, NodeDto>(left => left.NodeId, right => right.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
.InnerJoin<ContentTypeDto>(SqlSyntax)
.On<ContentTypeDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.ContentTypeId)
.InnerJoin<NodeDto>(SqlSyntax)
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
return sql;
}
protected override Sql GetBaseQuery(bool isCount)
{
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle);
}
protected override string GetBaseWhereClause()
@@ -380,90 +380,60 @@ namespace Umbraco.Core.Persistence.Repositories
#region Overrides of VersionableRepositoryBase<IMembershipUser>
public void RebuildXmlStructures(Func<IMember, XElement> serializer, int groupSize = 5000, IEnumerable<int> contentTypeIds = null)
public override IEnumerable<IMember> GetAllVersions(int id)
{
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { Id = id })
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
return ProcessQuery(sql, new PagingSqlQuery(sql), true);
}
//Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too.
using (var tr = Database.GetTransaction())
public void RebuildXmlStructures(Func<IMember, XElement> serializer, int groupSize = 200, IEnumerable<int> contentTypeIds = null)
{
// the previous way of doing this was to run it all in one big transaction,
// and to bulk-insert groups of xml rows - which works, until the transaction
// times out - and besides, because v7 transactions are ReadCommited, it does
// not bring much safety - so this reverts to updating each record individually,
// and it may be slower in the end, but should be more resilient.
var baseId = 0;
var contentTypeIdsA = contentTypeIds == null ? new int[0] : contentTypeIds.ToArray();
while (true)
{
//Remove all the data first, if anything fails after this it's no problem the transaction will be reverted
if (contentTypeIds == null)
{
var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member);
var subQuery = new Sql()
.Select("DISTINCT cmsContentXml.nodeId")
.From<ContentXmlDto>()
.InnerJoin<NodeDto>()
.On<ContentXmlDto, NodeDto>(left => left.NodeId, right => right.NodeId)
.Where<NodeDto>(dto => dto.NodeObjectType == memberObjectType);
// get the next group of nodes
var query = GetBaseQuery(false);
if (contentTypeIdsA.Length > 0)
query = query
.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
query = query
.Where<NodeDto>(x => x.NodeId > baseId)
.OrderBy<NodeDto>(x => x.NodeId, SqlSyntax);
var sql = SqlSyntax.SelectTop(query, groupSize);
var xmlItems = ProcessQuery(sql, new PagingSqlQuery(sql))
.Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() })
.ToList();
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
Database.Execute(deleteSql);
}
else
// no more nodes, break
if (xmlItems.Count == 0) break;
foreach (var xmlItem in xmlItems)
{
foreach (var id in contentTypeIds)
try
{
var id1 = id;
var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member);
var subQuery = new Sql()
.Select("DISTINCT cmsContentXml.nodeId")
.From<ContentXmlDto>()
.InnerJoin<NodeDto>()
.On<ContentXmlDto, NodeDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentDto>()
.On<ContentDto, NodeDto>(left => left.NodeId, right => right.NodeId)
.Where<NodeDto>(dto => dto.NodeObjectType == memberObjectType)
.Where<ContentDto>(dto => dto.ContentTypeId == id1);
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
Database.Execute(deleteSql);
// InsertOrUpdate tries to update first, which is good since it is what
// should happen in most cases, then it tries to insert, and it should work
// unless the node has been deleted, and we just report the exception
Database.InsertOrUpdate(xmlItem);
}
catch (Exception e)
{
Logger.Error<MediaRepository>("Could not rebuild XML for nodeId=" + xmlItem.NodeId, e);
}
}
//now insert the data, again if something fails here, the whole transaction is reversed
if (contentTypeIds == null)
{
var query = Query<IMember>.Builder;
RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize);
}
else
{
foreach (var contentTypeId in contentTypeIds)
{
//copy local
var id = contentTypeId;
var query = Query<IMember>.Builder.Where(x => x.ContentTypeId == id && x.Trashed == false);
RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize);
}
}
tr.Complete();
baseId = xmlItems.Last().NodeId;
}
}
private void RebuildXmlStructuresProcessQuery(Func<IMember, XElement> serializer, IQuery<IMember> query, Transaction tr, int pageSize)
{
var pageIndex = 0;
var total = long.MinValue;
var processed = 0;
do
{
var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, true);
var xmlItems = (from descendant in descendants
let xml = serializer(descendant)
select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray();
//bulk insert it into the database
Database.BulkInsertRecords(xmlItems, tr);
processed += xmlItems.Length;
pageIndex++;
} while (processed < total);
}
public override IMember GetByVersion(Guid versionId)
{
var sql = GetBaseQuery(false);
@@ -480,7 +450,7 @@ namespace Umbraco.Core.Persistence.Repositories
var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.NodeId);
var media = factory.BuildEntity(dto);
var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) });
var properties = GetPropertyCollection(new PagingSqlQuery(sql), new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) });
media.Properties = properties[dto.NodeId];
@@ -571,7 +541,7 @@ namespace Umbraco.Core.Persistence.Repositories
.OrderByDescending<ContentVersionDto>(x => x.VersionDate)
.OrderBy<NodeDto>(x => x.SortOrder);
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
@@ -653,9 +623,9 @@ namespace Umbraco.Core.Persistence.Repositories
filterCallback = () => new Tuple<string, object[]>(sbWhere.ToString().Trim(), args.ToArray());
}
return GetPagedResultsByQuery<MemberDto, Member>(query, pageIndex, pageSize, out totalRecords,
return GetPagedResultsByQuery<MemberDto>(query, pageIndex, pageSize, out totalRecords,
new Tuple<string, string>("cmsMember", "nodeId"),
ProcessQuery, orderBy, orderDirection, orderBySystemField,
(sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
@@ -695,59 +665,76 @@ namespace Umbraco.Core.Persistence.Repositories
return base.GetEntityPropertyNameForOrderBy(orderBy);
}
private IEnumerable<IMember> ProcessQuery(Sql sql)
{
//NOTE: This doesn't allow properties to be part of the query
var dtos = Database.Fetch<MemberDto, ContentVersionDto, ContentDto, NodeDto>(sql);
var ids = dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray();
//content types
var contentTypes = ids.Length == 0 ? Enumerable.Empty<IMemberType>() : _memberTypeRepository.GetAll(ids).ToArray();
var dtosWithContentTypes = dtos
//This select into and null check are required because we don't have a foreign damn key on the contentType column
// http://issues.umbraco.org/issue/U4-5503
.Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) })
.Where(x => x.contentType != null)
.ToArray();
//Go get the property data for each document
IEnumerable<DocumentDefinition> docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition(
d.dto.NodeId,
d.dto.ContentVersionDto.VersionId,
d.dto.ContentVersionDto.VersionDate,
d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate,
d.contentType));
var propertyData = GetPropertyCollection(sql, docDefs);
return dtosWithContentTypes.Select(d => CreateMemberFromDto(
d.dto,
contentTypes.First(ct => ct.Id == d.dto.ContentVersionDto.ContentDto.ContentTypeId),
propertyData[d.dto.NodeId]));
}
/// <summary>
/// Private method to create a member object from a MemberDto
/// This is the underlying method that processes most queries for this repository
/// </summary>
/// <param name="dto"></param>
/// <param name="contentType"></param>
/// <param name="propCollection"></param>
/// <param name="sqlFull">
/// The full SQL to select all member data
/// </param>
/// <param name="pagingSqlQuery">
/// The Id SQL to just return all member ids - used to process the properties for the member item
/// </param>
/// <param name="withCache"></param>
/// <returns></returns>
private IMember CreateMemberFromDto(MemberDto dto,
IMemberType contentType,
PropertyCollection propCollection)
private IEnumerable<IMember> ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false)
{
var factory = new MemberFactory(contentType, NodeObjectTypeId, dto.ContentVersionDto.NodeId);
var member = factory.BuildEntity(dto);
// fetch returns a list so it's ok to iterate it in this method
var dtos = Database.Fetch<MemberDto, ContentVersionDto, ContentDto, NodeDto>(sqlFull);
member.Properties = propCollection;
var content = new IMember[dtos.Count];
var defs = new List<DocumentDefinition>();
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((Entity)member).ResetDirtyProperties(false);
return member;
for (var i = 0; i < dtos.Count; i++)
{
var dto = dtos[i];
// if the cache contains the item, use it
if (withCache)
{
var cached = RuntimeCache.GetCacheItem<IMember>(GetCacheIdKey<IMember>(dto.NodeId));
if (cached != null)
{
content[i] = cached;
continue;
}
}
// else, need to fetch from the database
// content type repository is full-cache so OK to get each one independently
var contentType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
var factory = new MemberFactory(contentType, NodeObjectTypeId, dto.NodeId);
content[i] = factory.BuildEntity(dto);
// need properties
defs.Add(new DocumentDefinition(
dto.NodeId,
dto.ContentVersionDto.VersionId,
dto.ContentVersionDto.VersionDate,
dto.ContentVersionDto.ContentDto.NodeDto.CreateDate,
contentType
));
}
// load all properties for all documents from database in 1 query
var propertyData = GetPropertyCollection(pagingSqlQuery, defs);
// assign
var dtoIndex = 0;
foreach (var def in defs)
{
// move to corresponding item (which has to exist)
while (dtos[dtoIndex].NodeId != def.Id) dtoIndex++;
// complete the item
var cc = content[dtoIndex];
cc.Properties = propertyData[cc.Id];
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((Entity)cc).ResetDirtyProperties(false);
}
return content;
}
/// <summary>
@@ -63,6 +63,7 @@ namespace Umbraco.Core.Persistence.Repositories
FormatDeleteStatement("cmsContentVersion", "ContentId"),
FormatDeleteStatement("cmsContentXml", "nodeId"),
FormatDeleteStatement("cmsContent", "nodeId"),
//TODO: Why is this being done? We just delete this exact data in the next line
"UPDATE umbracoNode SET parentID = '" + RecycleBinId + "' WHERE trashed = '1' AND nodeObjectType = @NodeObjectType",
"DELETE FROM umbracoNode WHERE trashed = '1' AND nodeObjectType = @NodeObjectType"
};
@@ -91,14 +92,18 @@ namespace Umbraco.Core.Persistence.Repositories
}
}
/// <summary>
/// A delete statement that will delete anything in the table specified where its PK (keyName) is found in the
/// list of umbracoNode.id that have trashed flag set
/// </summary>
/// <param name="tableName"></param>
/// <param name="keyName"></param>
/// <returns></returns>
private string FormatDeleteStatement(string tableName, string keyName)
{
//This query works with sql ce and sql server:
//DELETE FROM umbracoUser2NodeNotify WHERE umbracoUser2NodeNotify.nodeId IN
//(SELECT nodeId FROM umbracoUser2NodeNotify as TB1 INNER JOIN umbracoNode as TB2 ON TB1.nodeId = TB2.id WHERE TB2.trashed = '1' AND TB2.nodeObjectType = 'C66BA18E-EAF3-4CFF-8A22-41B16D66A972')
return
string.Format(
"DELETE FROM {0} WHERE {0}.{1} IN (SELECT TB1.{1} FROM {0} as TB1 INNER JOIN umbracoNode as TB2 ON TB1.{1} = TB2.id WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)",
"DELETE FROM {0} WHERE {0}.{1} IN (SELECT id FROM umbracoNode WHERE trashed = '1' AND nodeObjectType = @NodeObjectType)",
tableName, keyName);
}
@@ -42,34 +42,17 @@ namespace Umbraco.Core.Persistence.Repositories
throw new Exception(string.Format("RelationType with Id: {0} doesn't exist", dto.RelationType));
var factory = new RelationFactory(relationType);
var entity = factory.BuildEntity(dto);
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((TracksChangesEntityBase)entity).ResetDirtyProperties(false);
return entity;
return DtoToEntity(dto, factory);
}
//TODO: Fix N+1 !
protected override IEnumerable<IRelation> PerformGetAll(params int[] ids)
{
if (ids.Any())
{
foreach (var id in ids)
{
yield return Get(id);
}
}
else
{
var dtos = Database.Fetch<RelationDto>("WHERE id > 0");
foreach (var dto in dtos)
{
yield return Get(dto.Id);
}
}
var sql = GetBaseQuery(false);
if (ids.Length > 0)
sql.WhereIn<RelationDto>(x => x.Id, ids);
sql.OrderBy<RelationDto>(x => x.RelationType);
var dtos = Database.Fetch<RelationDto>(sql);
return DtosToEntities(dtos);
}
protected override IEnumerable<IRelation> PerformGetByQuery(IQuery<IRelation> query)
@@ -77,13 +60,36 @@ namespace Umbraco.Core.Persistence.Repositories
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator<IRelation>(sqlClause, query);
var sql = translator.Translate();
sql.OrderBy<RelationDto>(x => x.RelationType);
var dtos = Database.Fetch<RelationDto>(sql);
return DtosToEntities(dtos);
}
foreach (var dto in dtos)
private IEnumerable<IRelation> DtosToEntities(IEnumerable<RelationDto> dtos)
{
// in most cases, the relation type will be the same for all of them,
// plus we've ordered the relations by type, so try to allocate as few
// factories as possible - bearing in mind that relation types are cached
RelationFactory factory = null;
var relationTypeId = -1;
return dtos.Select(x =>
{
yield return Get(dto.Id);
}
if (relationTypeId != x.RelationType)
factory = new RelationFactory(_relationTypeRepository.Get(relationTypeId = x.RelationType));
return DtoToEntity(x, factory);
});
}
private static IRelation DtoToEntity(RelationDto dto, RelationFactory factory)
{
var entity = factory.BuildEntity(dto);
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((TracksChangesEntityBase)entity).ResetDirtyProperties(false);
return entity;
}
#endregion
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
@@ -18,50 +19,42 @@ namespace Umbraco.Core.Persistence.Repositories
/// </summary>
internal class RelationTypeRepository : PetaPocoRepositoryBase<int, IRelationType>, IRelationTypeRepository
{
public RelationTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{ }
// assuming we don't have tons of relation types, use a FullDataSet policy, ie
// cache the entire GetAll result once in a single collection - which can expire
private FullDataSetRepositoryCachePolicyFactory<IRelationType, int> _cachePolicyFactory;
protected override IRepositoryCachePolicyFactory<IRelationType, int> CachePolicyFactory
{
get
{
return _cachePolicyFactory
?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory<IRelationType, int>(
RuntimeCache, GetEntityId, () => PerformGetAll(), expires: true));
}
}
#region Overrides of RepositoryBase<int,RelationType>
protected override IRelationType PerformGet(int id)
{
var sql = GetBaseQuery(false);
sql.Where(GetBaseWhereClause(), new { Id = id });
var dto = Database.Fetch<RelationTypeDto>(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault();
if (dto == null)
return null;
var factory = new RelationTypeFactory();
var entity = factory.BuildEntity(dto);
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((TracksChangesEntityBase)entity).ResetDirtyProperties(false);
return entity;
// use the underlying GetAll which will force cache all content types
return GetAll().FirstOrDefault(x => x.Id == id);
}
protected override IEnumerable<IRelationType> PerformGetAll(params int[] ids)
{
var sql = GetBaseQuery(false);
// should not happen due to the cache policy
if (ids.Any())
{
foreach (var id in ids)
{
yield return Get(id);
}
}
else
{
var dtos = Database.Fetch<RelationTypeDto>("WHERE id > 0");
foreach (var dto in dtos)
{
yield return Get(dto.Id);
}
}
throw new NotImplementedException();
var dtos = Database.Fetch<RelationTypeDto>(sql);
var factory = new RelationTypeFactory();
return dtos.Select(x => DtoToEntity(x, factory));
}
protected override IEnumerable<IRelationType> PerformGetByQuery(IQuery<IRelationType> query)
@@ -71,11 +64,19 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = translator.Translate();
var dtos = Database.Fetch<RelationTypeDto>(sql);
var factory = new RelationTypeFactory();
return dtos.Select(x => DtoToEntity(x, factory));
}
foreach (var dto in dtos)
{
yield return Get(dto.Id);
}
private static IRelationType DtoToEntity(RelationTypeDto dto, RelationTypeFactory factory)
{
var entity = factory.BuildEntity(dto);
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((TracksChangesEntityBase) entity).ResetDirtyProperties(false);
return entity;
}
#endregion
@@ -83,6 +83,12 @@ namespace Umbraco.Core.Persistence.Repositories
}
#region Static Queries
private IQuery<TEntity> _hasIdQuery;
#endregion
protected virtual TId GetEntityId(TEntity entity)
{
return (TId)(object)entity.Id;
@@ -111,9 +117,14 @@ namespace Umbraco.Core.Persistence.Repositories
RuntimeCache,
new RepositoryCachePolicyOptions(() =>
{
//create it once if it is needed (no need for locking here)
if (_hasIdQuery == null)
{
_hasIdQuery = Query<TEntity>.Builder.Where(x => x.Id != 0);
}
//Get count of all entities of current type (TEntity) to ensure cached result is correct
var query = Query<TEntity>.Builder.Where(x => x.Id != 0);
return PerformCount(query);
return PerformCount(_hasIdQuery);
})));
}
}
@@ -241,7 +241,12 @@ namespace Umbraco.Core.Persistence.Repositories
{
entity.Path = string.Concat(parent.Path, ",", entity.Id);
}
else
{
//this means that the master template has been removed, so we need to reset the template's
//path to be at the root
entity.Path = string.Concat("-1,", entity.Id);
}
}
//Get TemplateDto from db to get the Primary key of the entity
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
@@ -25,8 +27,6 @@ using Umbraco.Core.IO;
namespace Umbraco.Core.Persistence.Repositories
{
using SqlSyntax;
internal abstract class VersionableRepositoryBase<TId, TEntity> : PetaPocoRepositoryBase<TId, TEntity>
where TEntity : class, IAggregateRoot
{
@@ -137,6 +137,40 @@ namespace Umbraco.Core.Persistence.Repositories
#endregion
/// <summary>
/// Gets paged document descendants as XML by path
/// </summary>
/// <param name="path">Path starts with</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="orderBy"></param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of content items</returns>
public virtual IEnumerable<XElement> GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, string[] orderBy, out long totalRecords)
{
var query = new Sql().Select(string.Format("umbracoNode.id, cmsContentXml.{0}", SqlSyntax.GetQuotedColumnName("xml")))
.From("umbracoNode")
.InnerJoin("cmsContentXml").On("cmsContentXml.nodeId = umbracoNode.id");
if (path == "-1")
{
query.Where("umbracoNode.nodeObjectType = @type", new { type = NodeObjectTypeId });
}
else
{
query.Where(string.Format("umbracoNode.{0} LIKE (@0)", SqlSyntax.GetQuotedColumnName("path")), path.EnsureEndsWith(",%"));
}
//each order by param needs to be in a bracket! see: https://github.com/toptensoftware/PetaPoco/issues/177
query.OrderBy(orderBy == null
? "(umbracoNode.id)"
: string.Join(",", orderBy.Select(x => string.Format("({0})", SqlSyntax.GetQuotedColumnName(x)))));
var pagedResult = Database.Page<ContentXmlDto>(pageIndex + 1, pageSize, query);
totalRecords = pagedResult.TotalItems;
return pagedResult.Items.Select(dto => XElement.Parse(dto.Xml));
}
public int CountDescendants(int parentId, string contentTypeAlias = null)
{
var pathMatch = parentId == -1
@@ -348,12 +382,8 @@ namespace Umbraco.Core.Persistence.Repositories
ON CustomPropData.CustomPropValContentId = umbracoNode.id
", sortedInt, sortedDecimal, sortedDate, sortedString, nodeIdSelect.Item2, nodeIdSelect.Item1, versionQuery, sortedSql.Arguments.Length, newestQuery);
//insert this just above the first LEFT OUTER JOIN (for cmsDocument) or the last WHERE (everything else)
string newSql;
if (nodeIdSelect.Item1 == "cmsDocument")
newSql = sortedSql.SQL.Insert(sortedSql.SQL.IndexOf("LEFT OUTER JOIN"), outerJoinTempTable);
else
newSql = sortedSql.SQL.Insert(sortedSql.SQL.LastIndexOf("WHERE"), outerJoinTempTable);
//insert this just above the last WHERE
string newSql = sortedSql.SQL.Insert(sortedSql.SQL.LastIndexOf("WHERE"), outerJoinTempTable);
var newArgs = sortedSql.Arguments.ToList();
newArgs.Add(orderBy);
@@ -370,11 +400,14 @@ namespace Umbraco.Core.Persistence.Repositories
}
}
//no matter what we always MUST order the result also by umbracoNode.id to ensure that all records being ordered by are unique.
// if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column
// is empty for many nodes)
// see: http://issues.umbraco.org/issue/U4-8831
sortedSql.OrderBy("umbracoNode.id");
if (orderBySystemField && orderBy != "umbracoNode.id")
{
//no matter what we always MUST order the result also by umbracoNode.id to ensure that all records being ordered by are unique.
// if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column
// is empty for many nodes)
// see: http://issues.umbraco.org/issue/U4-8831
sortedSql.OrderBy("umbracoNode.id");
}
return sortedSql;
@@ -384,7 +417,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// A helper method for inheritors to get the paged results by query in a way that minimizes queries
/// </summary>
/// <typeparam name="TDto">The type of the d.</typeparam>
/// <typeparam name="TContentBase">The 'true' entity type (i.e. Content, Member, etc...)</typeparam>
/// <param name="query">The query.</param>
/// <param name="pageIndex">Index of the page.</param>
/// <param name="pageSize">Size of the page.</param>
@@ -397,34 +429,30 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="orderBySystemField">Flag to indicate when ordering by system field</param>
/// <returns></returns>
/// <exception cref="System.ArgumentNullException">orderBy</exception>
protected IEnumerable<TEntity> GetPagedResultsByQuery<TDto, TContentBase>(IQuery<TEntity> query, long pageIndex, int pageSize, out long totalRecords,
protected IEnumerable<TEntity> GetPagedResultsByQuery<TDto>(IQuery<TEntity> query, long pageIndex, int pageSize, out long totalRecords,
Tuple<string, string> nodeIdSelect,
Func<Sql, IEnumerable<TEntity>> processQuery,
Func<Sql, PagingSqlQuery<TDto>, IEnumerable<TEntity>> processQuery,
string orderBy,
Direction orderDirection,
bool orderBySystemField,
Func<Tuple<string, object[]>> defaultFilter = null)
where TContentBase : class, IAggregateRoot, TEntity
{
if (orderBy == null) throw new ArgumentNullException("orderBy");
// Get base query
var sqlBase = GetBaseQuery(false);
// Get base query for returning IDs
var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids);
// Get base query for returning all data
var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple);
if (query == null) query = new Query<TEntity>();
var translator = new SqlTranslator<TEntity>(sqlBase, query);
var sqlQuery = translator.Translate();
// Note we can't do multi-page for several DTOs like we can multi-fetch and are doing in PerformGetByQuery,
// but actually given we are doing a Get on each one (again as in PerformGetByQuery), we only need the node Id.
// So we'll modify the SQL.
var sqlNodeIds = new Sql(
sqlQuery.SQL.Replace("SELECT *", string.Format("SELECT {0}.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2)),
sqlQuery.Arguments);
var translatorIds = new SqlTranslator<TEntity>(sqlBaseIds, query);
var sqlQueryIds = translatorIds.Translate();
var translatorFull = new SqlTranslator<TEntity>(sqlBaseFull, query);
var sqlQueryFull = translatorFull.Translate();
//get sorted and filtered sql
var sqlNodeIdsWithSort = GetSortedSqlForPagedResults(
GetFilteredSqlForPagedResults(sqlNodeIds, defaultFilter),
GetFilteredSqlForPagedResults(sqlQueryIds, defaultFilter),
orderDirection, orderBy, orderBySystemField, nodeIdSelect);
// Get page of results and total count
@@ -433,38 +461,32 @@ namespace Umbraco.Core.Persistence.Repositories
totalRecords = Convert.ToInt32(pagedResult.TotalItems);
//NOTE: We need to check the actual items returned, not the 'totalRecords', that is because if you request a page number
// that doesn't actually have any data on it, the totalRecords will still indicate there are records but there are none in
// that doesn't actually have any data on it, the totalRecords will still indicate there are records but there are none in
// the pageResult, then the GetAll will actually return ALL records in the db.
if (pagedResult.Items.Any())
{
//Crete the inner paged query that was used above to get the paged result, we'll use that as the inner sub query
//Create the inner paged query that was used above to get the paged result, we'll use that as the inner sub query
var args = sqlNodeIdsWithSort.Arguments;
string sqlStringCount, sqlStringPage;
Database.BuildPageQueries<TDto>(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage);
//if this is for sql server, the sqlPage will start with a SELECT * but we don't want that, we only want to return the nodeId
sqlStringPage = sqlStringPage
.Replace("SELECT *",
//This ensures we only take the field name of the node id select and not the table name - since the resulting select
// will ony work with the field name.
"SELECT " + nodeIdSelect.Item2);
//We need to make this an inner join on the paged query
var splitQuery = sqlQuery.SQL.Split(new[] { "WHERE " }, StringSplitOptions.None);
var withInnerJoinSql = new Sql(splitQuery[0])
//We need to make this FULL query an inner join on the paged ID query
var splitQuery = sqlQueryFull.SQL.Split(new[] { "WHERE " }, StringSplitOptions.None);
var fullQueryWithPagedInnerJoin = new Sql(splitQuery[0])
.Append("INNER JOIN (")
//join the paged query with the paged query arguments
.Append(sqlStringPage, args)
.Append(") temp ")
.Append(string.Format("ON {0}.{1} = temp.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2))
//add the original where clause back with the original arguments
.Where(splitQuery[1], sqlQuery.Arguments);
.Where(splitQuery[1], sqlQueryIds.Arguments);
//get sorted and filtered sql
var fullQuery = GetSortedSqlForPagedResults(
GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter),
GetFilteredSqlForPagedResults(fullQueryWithPagedInnerJoin, defaultFilter),
orderDirection, orderBy, orderBySystemField, nodeIdSelect);
return processQuery(fullQuery);
return processQuery(fullQuery, new PagingSqlQuery<TDto>(Database, sqlNodeIdsWithSort, pageIndex, pageSize));
}
else
{
@@ -474,38 +496,56 @@ namespace Umbraco.Core.Persistence.Repositories
return result;
}
/// <summary>
/// Gets the property collection for a non-paged query
/// </summary>
/// <param name="sql"></param>
/// <param name="documentDefs"></param>
/// <returns></returns>
protected IDictionary<int, PropertyCollection> GetPropertyCollection(
Sql docSql,
IEnumerable<DocumentDefinition> documentDefs)
Sql sql,
IReadOnlyCollection<DocumentDefinition> documentDefs)
{
if (documentDefs.Any() == false) return new Dictionary<int, PropertyCollection>();
return GetPropertyCollection(new PagingSqlQuery(sql), documentDefs);
}
//we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use
/// <summary>
/// Gets the property collection for a query
/// </summary>
/// <param name="pagingSqlQuery"></param>
/// <param name="documentDefs"></param>
/// <returns></returns>
protected IDictionary<int, PropertyCollection> GetPropertyCollection(
PagingSqlQuery pagingSqlQuery,
IReadOnlyCollection<DocumentDefinition> documentDefs)
{
if (documentDefs.Count == 0) return new Dictionary<int, PropertyCollection>();
//initialize to the query passed in
var docSql = pagingSqlQuery.PrePagedSql;
//we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use
// the statement to go get the property data for all of the items by using an inner join
var parsedOriginalSql = "SELECT {0} " + docSql.SQL.Substring(docSql.SQL.IndexOf("FROM", StringComparison.Ordinal));
//now remove everything from an Orderby clause and beyond
if (parsedOriginalSql.InvariantContains("ORDER BY "))
if (pagingSqlQuery.HasPaging)
{
//if this is a paged query, build the paged query with the custom column substitution, then re-assign
docSql = pagingSqlQuery.BuildPagedQuery("{0}");
parsedOriginalSql = docSql.SQL;
}
else if (parsedOriginalSql.InvariantContains("ORDER BY "))
{
//now remove everything from an Orderby clause and beyond if this is unpaged data
parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal));
}
var propSql = new Sql(@"SELECT cmsPropertyData.*
FROM cmsPropertyData
INNER JOIN cmsPropertyType
ON cmsPropertyData.propertytypeid = cmsPropertyType.id
INNER JOIN
(" + string.Format(parsedOriginalSql, "cmsContent.nodeId, cmsContentVersion.VersionId") + @") as docData
ON cmsPropertyData.versionId = docData.VersionId AND cmsPropertyData.contentNodeId = docData.nodeId
LEFT OUTER JOIN cmsDataTypePreValues
ON cmsPropertyType.dataTypeId = cmsDataTypePreValues.datatypeNodeId", docSql.Arguments);
var allPropertyData = Database.Fetch<PropertyDataDto>(propSql);
//This is a lazy access call to get all prevalue data for the data types that make up all of these properties which we use
// below if any property requires tag support
var allPreValues = new Lazy<IEnumerable<DataTypePreValueDto>>(() =>
{
var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId
//This retrieves all pre-values for all data types that are referenced for all property types
// that exist in the data set.
//Benchmarks show that eagerly loading these so that we can lazily read the property data
// below (with the use of Query intead of Fetch) go about 30% faster, so we'll eagerly load
// this now since we cannot execute another reader inside of reading the property data.
var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId
FROM cmsDataTypePreValues a
WHERE EXISTS(
SELECT DISTINCT b.id as preValIdInner
@@ -513,29 +553,82 @@ WHERE EXISTS(
INNER JOIN cmsPropertyType
ON b.datatypeNodeId = cmsPropertyType.dataTypeId
INNER JOIN
(" + string.Format(parsedOriginalSql, "DISTINCT cmsContent.contentType") + @") as docData
(" + string.Format(parsedOriginalSql, "cmsContent.contentType") + @") as docData
ON cmsPropertyType.contentTypeId = docData.contentType
WHERE a.id = b.id)", docSql.Arguments);
return Database.Fetch<DataTypePreValueDto>(preValsSql);
});
var allPreValues = Database.Fetch<DataTypePreValueDto>(preValsSql);
//It's Important with the sort order here! We require this to be sorted by node id,
// this is required because this data set can be huge depending on the page size. Due
// to it's size we need to be smart about iterating over the property values to build
// the document. Before we used to use Linq to get the property data for a given content node
// and perform a Distinct() call. This kills performance because that would mean if we had 7000 nodes
// and on each iteration we will perform a lookup on potentially 100,000 property rows against the node
// id which turns out to be a crazy amount of iterations. Instead we know it's sorted by this value we'll
// keep an index stored of the rows being read so we never have to re-iterate the entire data set
// on each document iteration.
var propSql = new Sql(@"SELECT cmsPropertyData.*
FROM cmsPropertyData
INNER JOIN cmsPropertyType
ON cmsPropertyData.propertytypeid = cmsPropertyType.id
INNER JOIN
(" + string.Format(parsedOriginalSql, "cmsContent.nodeId, cmsContentVersion.VersionId") + @") as docData
ON cmsPropertyData.versionId = docData.VersionId AND cmsPropertyData.contentNodeId = docData.nodeId
ORDER BY contentNodeId, propertytypeid
", docSql.Arguments);
//This does NOT fetch all data into memory in a list, this will read
// over the records as a data reader, this is much better for performance and memory,
// but it means that during the reading of this data set, nothing else can be read
// from SQL server otherwise we'll get an exception.
var allPropertyData = Database.Query<PropertyDataDto>(propSql);
var result = new Dictionary<int, PropertyCollection>();
var propertiesWithTagSupport = new Dictionary<string, SupportTagsAttribute>();
//used to track the resolved composition property types per content type so we don't have to re-resolve (ToArray) the list every time
var resolvedCompositionProperties = new Dictionary<int, PropertyType[]>();
//iterate each definition grouped by it's content type - this will mean less property type iterations while building
// up the property collections
foreach (var compositionGroup in documentDefs.GroupBy(x => x.Composition))
//keep track of the current property data item being enumerated
var propertyDataSetEnumerator = allPropertyData.GetEnumerator();
var hasCurrent = false; // initially there is no enumerator.Current
try
{
var compositionProperties = compositionGroup.Key.CompositionPropertyTypes.ToArray();
foreach (var def in compositionGroup)
//This must be sorted by node id because this is how we are sorting the query to lookup property types above,
// which allows us to more efficiently iterate over the large data set of property values
foreach (var def in documentDefs.OrderBy(x => x.Id))
{
var propertyDataDtos = allPropertyData.Where(x => x.NodeId == def.Id).Distinct();
// get the resolved properties from our local cache, or resolve them and put them in cache
PropertyType[] compositionProperties;
if (resolvedCompositionProperties.ContainsKey(def.Composition.Id))
{
compositionProperties = resolvedCompositionProperties[def.Composition.Id];
}
else
{
compositionProperties = def.Composition.CompositionPropertyTypes.ToArray();
resolvedCompositionProperties[def.Composition.Id] = compositionProperties;
}
var propertyFactory = new PropertyFactory(compositionProperties, def.Version, def.Id, def.CreateDate, def.VersionDate);
var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray();
// assemble the dtos for this def
// use the available enumerator.Current if any else move to next
var propertyDataDtos = new List<PropertyDataDto>();
while (hasCurrent || propertyDataSetEnumerator.MoveNext())
{
if (propertyDataSetEnumerator.Current.NodeId == def.Id)
{
hasCurrent = false; // enumerator.Current is not available
propertyDataDtos.Add(propertyDataSetEnumerator.Current);
}
else
{
hasCurrent = true; // enumerator.Current is available for another def
break; // no more propertyDataDto for this def
}
}
var properties = PropertyFactory.BuildEntity(propertyDataDtos, compositionProperties, def.CreateDate, def.VersionDate).ToArray();
foreach (var property in properties)
{
@@ -552,7 +645,7 @@ WHERE EXISTS(
propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] = tagSupport;
//this property has tags, so we need to extract them and for that we need the prevals which we've already looked up
var preValData = allPreValues.Value.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId)
var preValData = allPreValues.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId)
.Distinct()
.ToArray();
@@ -575,31 +668,15 @@ WHERE EXISTS(
result[def.Id] = new PropertyCollection(properties);
}
}
return result;
}
public class DocumentDefinition
{
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
/// </summary>
public DocumentDefinition(int id, Guid version, DateTime versionDate, DateTime createDate, IContentTypeComposition composition)
finally
{
Id = id;
Version = version;
VersionDate = versionDate;
CreateDate = createDate;
Composition = composition;
propertyDataSetEnumerator.Dispose();
}
public int Id { get; set; }
public Guid Version { get; set; }
public DateTime VersionDate { get; set; }
public DateTime CreateDate { get; set; }
public IContentTypeComposition Composition { get; set; }
}
return result;
}
protected virtual string GetDatabaseFieldNameForOrderBy(string orderBy)
{
// Translate the passed order by field (which were originally defined for in-memory object sorting
@@ -688,5 +765,99 @@ WHERE EXISTS(
return allsuccess;
}
/// <summary>
/// For Paging, repositories must support returning different query for the query type specified
/// </summary>
/// <param name="queryType"></param>
/// <returns></returns>
protected abstract Sql GetBaseQuery(BaseQueryType queryType);
internal class DocumentDefinition
{
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
/// </summary>
public DocumentDefinition(int id, Guid version, DateTime versionDate, DateTime createDate, IContentTypeComposition composition)
{
Id = id;
Version = version;
VersionDate = versionDate;
CreateDate = createDate;
Composition = composition;
}
public int Id { get; set; }
public Guid Version { get; set; }
public DateTime VersionDate { get; set; }
public DateTime CreateDate { get; set; }
public IContentTypeComposition Composition { get; set; }
}
/// <summary>
/// An object representing a query that may contain paging information
/// </summary>
internal class PagingSqlQuery
{
public Sql PrePagedSql { get; private set; }
public PagingSqlQuery(Sql prePagedSql)
{
PrePagedSql = prePagedSql;
}
public virtual bool HasPaging
{
get { return false; }
}
public virtual Sql BuildPagedQuery(string selectColumns)
{
throw new InvalidOperationException("This query has no paging information");
}
}
/// <summary>
/// An object representing a query that contains paging information
/// </summary>
/// <typeparam name="T"></typeparam>
internal class PagingSqlQuery<T> : PagingSqlQuery
{
private readonly Database _db;
private readonly long _pageIndex;
private readonly int _pageSize;
public PagingSqlQuery(Database db, Sql prePagedSql, long pageIndex, int pageSize) : base(prePagedSql)
{
_db = db;
_pageIndex = pageIndex;
_pageSize = pageSize;
}
public override bool HasPaging
{
get { return _pageSize > 0; }
}
/// <summary>
/// Creates a paged query based on the original query and subtitutes the selectColumns specified
/// </summary>
/// <param name="selectColumns"></param>
/// <returns></returns>
public override Sql BuildPagedQuery(string selectColumns)
{
if (HasPaging == false) throw new InvalidOperationException("This query has no paging information");
var resultSql = string.Format("SELECT {0} {1}", selectColumns, PrePagedSql.SQL.Substring(PrePagedSql.SQL.IndexOf("FROM", StringComparison.Ordinal)));
//this query is meant to be paged so we need to generate the paging syntax
//Create the inner paged query that was used above to get the paged result, we'll use that as the inner sub query
var args = PrePagedSql.Arguments;
string sqlStringCount, sqlStringPage;
_db.BuildPageQueries<T>(_pageIndex * _pageSize, _pageSize, resultSql, ref args, out sqlStringCount, out sqlStringPage);
return new Sql(sqlStringPage, args);
}
}
}
}
@@ -197,7 +197,7 @@ namespace Umbraco.Core.Persistence
{
return new RelationRepository(
uow,
_noCache, //never cache
_noCache,
_logger, _sqlSyntax,
CreateRelationTypeRepository(uow));
}
@@ -206,7 +206,7 @@ namespace Umbraco.Core.Persistence
{
return new RelationTypeRepository(
uow,
_noCache, //never cache
_cacheHelper,
_logger, _sqlSyntax);
}
@@ -343,4 +343,4 @@ namespace Umbraco.Core.Persistence
_sqlSyntax);
}
}
}
}
@@ -4,6 +4,7 @@ using System.Data;
namespace Umbraco.Core.Persistence.SqlSyntax
{
//TODO: TSyntax should be removed, it's not used/needed here
public class DbTypes<TSyntax>
where TSyntax : ISqlSyntaxProvider
{
@@ -1,4 +1,6 @@
using System;
using System.Data;
using System.Linq;
using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.SqlSyntax
@@ -133,5 +135,111 @@ namespace Umbraco.Core.Persistence.SqlSyntax
throw new ArgumentOutOfRangeException("columnType");
}
}
/// <summary>
/// This uses a the DbTypeMap created and custom mapping to resolve the SqlDbType
/// </summary>
/// <param name="clrType"></param>
/// <returns></returns>
public virtual SqlDbType GetSqlDbType(Type clrType)
{
var dbType = DbTypeMap.ColumnDbTypeMap.First(x => x.Key == clrType).Value;
return GetSqlDbType(dbType);
}
/// <summary>
/// Returns the mapped SqlDbType for the DbType specified
/// </summary>
/// <param name="dbType"></param>
/// <returns></returns>
public virtual SqlDbType GetSqlDbType(DbType dbType)
{
var sqlDbType = SqlDbType.NVarChar;
//SEE: https://msdn.microsoft.com/en-us/library/cc716729(v=vs.110).aspx
// and https://msdn.microsoft.com/en-us/library/yy6y35y8%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
switch (dbType)
{
case DbType.AnsiString:
sqlDbType = SqlDbType.VarChar;
break;
case DbType.Binary:
sqlDbType = SqlDbType.VarBinary;
break;
case DbType.Byte:
sqlDbType = SqlDbType.TinyInt;
break;
case DbType.Boolean:
sqlDbType = SqlDbType.Bit;
break;
case DbType.Currency:
sqlDbType = SqlDbType.Money;
break;
case DbType.Date:
sqlDbType = SqlDbType.Date;
break;
case DbType.DateTime:
sqlDbType = SqlDbType.DateTime;
break;
case DbType.Decimal:
sqlDbType = SqlDbType.Decimal;
break;
case DbType.Double:
sqlDbType = SqlDbType.Float;
break;
case DbType.Guid:
sqlDbType = SqlDbType.UniqueIdentifier;
break;
case DbType.Int16:
sqlDbType = SqlDbType.SmallInt;
break;
case DbType.Int32:
sqlDbType = SqlDbType.Int;
break;
case DbType.Int64:
sqlDbType = SqlDbType.BigInt;
break;
case DbType.Object:
sqlDbType = SqlDbType.Variant;
break;
case DbType.SByte:
throw new NotSupportedException("Inferring a SqlDbType from SByte is not supported.");
case DbType.Single:
sqlDbType = SqlDbType.Real;
break;
case DbType.String:
sqlDbType = SqlDbType.NVarChar;
break;
case DbType.Time:
sqlDbType = SqlDbType.Time;
break;
case DbType.UInt16:
throw new NotSupportedException("Inferring a SqlDbType from UInt16 is not supported.");
case DbType.UInt32:
throw new NotSupportedException("Inferring a SqlDbType from UInt32 is not supported.");
case DbType.UInt64:
throw new NotSupportedException("Inferring a SqlDbType from UInt64 is not supported.");
case DbType.VarNumeric:
throw new NotSupportedException("Inferring a VarNumeric from UInt64 is not supported.");
case DbType.AnsiStringFixedLength:
sqlDbType = SqlDbType.Char;
break;
case DbType.StringFixedLength:
sqlDbType = SqlDbType.NChar;
break;
case DbType.Xml:
sqlDbType = SqlDbType.Xml;
break;
case DbType.DateTime2:
sqlDbType = SqlDbType.DateTime2;
break;
case DbType.DateTimeOffset:
sqlDbType = SqlDbType.DateTimeOffset;
break;
default:
throw new ArgumentOutOfRangeException();
}
return sqlDbType;
}
}
}
@@ -6,16 +6,11 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions;
namespace Umbraco.Core.Persistence.SqlSyntax
{
/// <summary>
/// Represents an SqlSyntaxProvider for Sql Server
/// Represents an SqlSyntaxProvider for Sql Server.
/// </summary>
[SqlSyntaxProviderAttribute(Constants.DatabaseProviders.SqlServer)]
[SqlSyntaxProvider(Constants.DatabaseProviders.SqlServer)]
public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase<SqlServerSyntaxProvider>
{
public SqlServerSyntaxProvider()
{
}
/// <summary>
/// Gets/sets the version of the current SQL server instance
/// </summary>
@@ -31,7 +26,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
switch (firstPart)
{
case "13":
_versionName = SqlServerVersionName.V2014;
_versionName = SqlServerVersionName.V2016;
break;
case "12":
_versionName = SqlServerVersionName.V2014;
@@ -75,7 +70,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
{
var items = db.Fetch<dynamic>("SELECT TableName = t.Name,ColumnName = c.Name,dc.Name,dc.[Definition] FROM sys.tables t INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id");
return items.Select(x => new Tuple<string, string, string, string>(x.TableName, x.ColumnName, x.Name, x.Definition));
}
}
public override IEnumerable<string> GetTablesInSchema(Database db)
{
@@ -120,9 +115,9 @@ from sys.tables as T inner join sys.indexes as I on T.[object_id] = I.[object_id
inner join sys.all_columns as AC on IC.[object_id] = AC.[object_id] and IC.[column_id] = AC.[column_id]
WHERE I.name NOT LIKE 'PK_%'
order by T.name, I.name");
return items.Select(item => new Tuple<string, string, string, bool>(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME,
return items.Select(item => new Tuple<string, string, string, bool>(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME,
item.UNIQUE == 1)).ToList();
}
public override bool DoesTableExist(Database db, string tableName)
@@ -164,7 +159,7 @@ order by T.name, I.name");
switch (systemMethod)
{
case SystemMethods.NewGuid:
return "NEWID()";
return "NEWID()";
case SystemMethods.CurrentDateTime:
return "GETDATE()";
//case SystemMethods.NewSequentialId:
@@ -181,11 +176,11 @@ order by T.name, I.name");
get { return "ALTER TABLE [{0}] DROP CONSTRAINT [DF_{0}_{1}]"; }
}
public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } }
public override string RenameColumn { get { return "sp_rename '{0}.{1}', '{2}', 'COLUMN'"; } }
}
}
@@ -32,6 +32,7 @@ using System.Security.Permissions;
[assembly: InternalsVisibleTo("umbraco.editorControls")]
[assembly: InternalsVisibleTo("Umbraco.Tests")]
[assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")]
[assembly: InternalsVisibleTo("Umbraco.Core")]
[assembly: InternalsVisibleTo("Umbraco.Web")]
[assembly: InternalsVisibleTo("Umbraco.Web.UI")]
@@ -114,7 +114,7 @@ namespace Umbraco.Core.Security
AllowRefresh = true,
IssuedUtc = nowUtc,
ExpiresUtc = nowUtc.AddMinutes(GlobalSettings.TimeOutInMinutes)
}, userIdentity, rememberBrowserIdentity);
}, userIdentity, rememberBrowserIdentity);
}
else
{
@@ -127,6 +127,10 @@ namespace Umbraco.Core.Security
}, userIdentity);
}
//track the last login date
user.LastLoginDateUtc = DateTime.UtcNow;
await UserManager.UpdateAsync(user);
_logger.WriteCore(TraceEventType.Information, 0,
string.Format(
"Login attempt succeeded for username {0} from IP address {1}",
@@ -625,6 +625,12 @@ namespace Umbraco.Core.Security
var anythingChanged = false;
//don't assign anything if nothing has changed as this will trigger
//the track changes of the model
if ((user.LastLoginDate != default(DateTime) && identityUser.LastLoginDateUtc.HasValue == false)
|| identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value)
{
anythingChanged = true;
user.LastLoginDate = identityUser.LastLoginDateUtc.Value.ToLocalTime();
}
if (user.Name != identityUser.Name && identityUser.Name.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
@@ -644,6 +650,13 @@ namespace Umbraco.Core.Security
{
anythingChanged = true;
user.IsLockedOut = identityUser.IsLockedOut;
if (user.IsLockedOut)
{
//need to set the last lockout date
user.LastLockoutDate = DateTime.Now;
}
}
if (user.Username != identityUser.UserName && identityUser.UserName.IsNullOrWhiteSpace() == false)
{
+81 -8
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml;
@@ -11,6 +12,7 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
@@ -56,6 +58,12 @@ namespace Umbraco.Core.Services
_userService = userService;
}
#region Static Queries
private IQuery<IContent> _notTrashedQuery;
#endregion
public int CountPublished(string contentTypeAlias = null)
{
var uow = UowProvider.GetUnitOfWork();
@@ -577,7 +585,7 @@ namespace Umbraco.Core.Services
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public IEnumerable<IContent> GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "")
public IEnumerable<IContent> GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
{
long total;
var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter);
@@ -596,7 +604,7 @@ namespace Umbraco.Core.Services
/// <param name="orderDirection">Direction to order by</param>
/// <param name="filter">Search text filter</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable<IContent> GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "")
public IEnumerable<IContent> GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
{
return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter);
}
@@ -708,7 +716,12 @@ namespace Umbraco.Core.Services
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable<IContent> GetDescendants(IContent content)
{
using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork()))
//This is a check to ensure that the path is correct for this entity to avoid problems like: http://issues.umbraco.org/issue/U4-9336 due to data corruption
if (content.ValidatePath() == false)
throw new InvalidDataException(string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", content.Id, content.Path, content.ParentId));
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateContentRepository(uow))
{
var pathMatch = content.Path + ",";
var query = Query<IContent>.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != content.Id);
@@ -787,10 +800,15 @@ namespace Umbraco.Core.Services
/// <returns></returns>
internal IEnumerable<IContent> GetAllPublished()
{
//create it once if it is needed (no need for locking here)
if (_notTrashedQuery == null)
{
_notTrashedQuery = Query<IContent>.Builder.Where(x => x.Trashed == false);
}
using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork()))
{
var query = Query<IContent>.Builder.Where(x => x.Trashed == false);
return repository.GetByPublishedVersion(query);
return repository.GetByPublishedVersion(_notTrashedQuery);
}
}
@@ -985,6 +1003,10 @@ namespace Umbraco.Core.Services
using (new WriteLock(Locker))
{
//Hack: this ensures that the entity's path is valid and if not it fixes/persists it
//see: http://issues.umbraco.org/issue/U4-9336
content.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate);
var originalPath = content.Path;
if (Trashing.IsRaisedEventCancelled(
@@ -1672,16 +1694,16 @@ namespace Umbraco.Core.Services
/// <returns>True if sorting succeeded, otherwise False</returns>
public bool Sort(IEnumerable<IContent> items, int userId = 0, bool raiseEvents = true)
{
var asArray = items.ToArray();
if (raiseEvents)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IContent>(items), this))
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IContent>(asArray), this))
return false;
}
var shouldBePublished = new List<IContent>();
var shouldBeSaved = new List<IContent>();
var asArray = items.ToArray();
using (new WriteLock(Locker))
{
var uow = UowProvider.GetUnitOfWork();
@@ -1742,6 +1764,31 @@ namespace Umbraco.Core.Services
return true;
}
/// <summary>
/// Gets paged content descendants as XML by path
/// </summary>
/// <param name="path">Path starts with</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of content items</returns>
public IEnumerable<XElement> GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords)
{
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
Mandate.ParameterCondition(pageSize > 0, "pageSize");
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateContentRepository(uow))
{
var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize,
//This order by is VERY important! This allows us to figure out what is implicitly not published, see ContentRepository.BuildXmlCache and
// UmbracoContentIndexer.PerformIndexAll which uses the logic based on this sort order
new[] {"level", "parentID", "sortOrder"},
out totalRecords);
return contents;
}
}
/// <summary>
/// This builds the Xml document used for the XML cache
/// </summary>
@@ -1801,6 +1848,23 @@ namespace Umbraco.Core.Services
#region Private Methods
/// <summary>
/// Hack: This is used to fix some data if an entity's properties are invalid/corrupt
/// </summary>
/// <param name="content"></param>
private void QuickUpdate(IContent content)
{
if (content == null) throw new ArgumentNullException("content");
if (content.HasIdentity == false) throw new InvalidOperationException("Cannot update an entity without an Identity");
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateContentRepository(uow))
{
repository.AddOrUpdate(content);
uow.Commit();
}
}
private void Audit(AuditType type, string message, int userId, int objectId)
{
var uow = UowProvider.GetUnitOfWork();
@@ -1908,6 +1972,10 @@ namespace Umbraco.Core.Services
using (new WriteLock(Locker))
{
//Hack: this ensures that the entity's path is valid and if not it fixes/persists it
//see: http://issues.umbraco.org/issue/U4-9336
content.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate);
var result = new List<Attempt<PublishStatus>>();
//Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published
@@ -2149,6 +2217,11 @@ namespace Umbraco.Core.Services
}
}
if (string.IsNullOrWhiteSpace(content.Name))
{
throw new ArgumentException("Cannot save content with empty name.");
}
using (new WriteLock(Locker))
{
var uow = UowProvider.GetUnitOfWork();
@@ -141,7 +141,7 @@ namespace Umbraco.Core.Services
{
var evtMsgs = EventMessagesFactory.Get();
if (container.ContainedObjectType != containerObjectType)
if (container.ContainerObjectType != containerObjectType)
{
var ex = new InvalidOperationException("Not a " + objectTypeName + " container.");
return OperationStatus.Exception(evtMsgs, ex);
@@ -719,8 +719,13 @@ namespace Umbraco.Core.Services
/// <param name="userId">Optional id of the user saving the ContentType</param>
public void Save(IContentType contentType, int userId = 0)
{
if (SavingContentType.IsRaisedEventCancelled(new SaveEventArgs<IContentType>(contentType), this))
return;
if (SavingContentType.IsRaisedEventCancelled(new SaveEventArgs<IContentType>(contentType), this))
return;
if (string.IsNullOrWhiteSpace(contentType.Name))
{
throw new ArgumentException("Cannot save content type with empty name.");
}
using (new WriteLock(Locker))
{
@@ -799,7 +804,7 @@ namespace Umbraco.Core.Services
// of a different type, move them to the recycle bin, then permanently delete the content items.
// The main problem with this is that for every content item being deleted, events are raised...
// which we need for many things like keeping caches in sync, but we can surely do this MUCH better.
var deletedContentTypes = new List<IContentType>() {contentType};
deletedContentTypes.AddRange(contentType.Descendants().OfType<IContentType>());
@@ -807,7 +812,7 @@ namespace Umbraco.Core.Services
{
_contentService.DeleteContentOfType(deletedContentType.Id);
}
repository.Delete(contentType);
uow.Commit();
+7 -2
View File
@@ -361,8 +361,13 @@ namespace Umbraco.Core.Services
/// <param name="userId">Id of the user issueing the save</param>
public void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition), this))
return;
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition), this))
return;
if (string.IsNullOrWhiteSpace(dataTypeDefinition.Name))
{
throw new ArgumentException("Cannot save datatype with empty name.");
}
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow))
+13 -2
View File
@@ -62,6 +62,12 @@ namespace Umbraco.Core.Services
}
#region Static Queries
private IQuery<IUmbracoEntity> _rootEntityQuery;
#endregion
/// <summary>
/// Returns the integer id for a given GUID
/// </summary>
@@ -386,11 +392,16 @@ namespace Umbraco.Core.Services
/// <returns>An enumerable list of <see cref="IUmbracoEntity"/> objects</returns>
public virtual IEnumerable<IUmbracoEntity> GetRootEntities(UmbracoObjectTypes umbracoObjectType)
{
//create it once if it is needed (no need for locking here)
if (_rootEntityQuery == null)
{
_rootEntityQuery = Query<IUmbracoEntity>.Builder.Where(x => x.ParentId == -1);
}
var objectTypeId = umbracoObjectType.GetGuid();
using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork()))
{
var query = Query<IUmbracoEntity>.Builder.Where(x => x.ParentId == -1);
var entities = repository.GetByQuery(query, objectTypeId);
var entities = repository.GetByQuery(_rootEntityQuery, objectTypeId);
return entities;
}
+16 -2
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml;
using System.Xml.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
@@ -95,6 +96,19 @@ namespace Umbraco.Core.Services
/// </summary>
public interface IContentService : IService
{
/// <summary>
/// Gets all XML entries found in the cmsContentXml table based on the given path
/// </summary>
/// <param name="path">Path starts with</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of content items</returns>
/// <remarks>
/// If -1 is passed, then this will return all content xml entries, otherwise will return all descendents from the path
/// </remarks>
IEnumerable<XElement> GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords);
/// <summary>
/// This builds the Xml document used for the XML cache
/// </summary>
@@ -245,7 +259,7 @@ namespace Umbraco.Core.Services
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
IEnumerable<IContent> GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords,
string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "");
string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "");
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by Parent Id
@@ -259,7 +273,7 @@ namespace Umbraco.Core.Services
/// <param name="filter">Search text filter</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
IEnumerable<IContent> GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords,
string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "");
string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "");
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by Parent Id
+16 -2
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
@@ -57,6 +58,19 @@ namespace Umbraco.Core.Services
/// </summary>
public interface IMediaService : IService
{
/// <summary>
/// Gets all XML entries found in the cmsContentXml table based on the given path
/// </summary>
/// <param name="path">Path starts with</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of media items</returns>
/// <remarks>
/// If -1 is passed, then this will return all media xml entries, otherwise will return all descendents from the path
/// </remarks>
IEnumerable<XElement> GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords);
/// <summary>
/// Rebuilds all xml content in the cmsContentXml table for all media
/// </summary>
@@ -155,7 +169,7 @@ namespace Umbraco.Core.Services
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
IEnumerable<IMedia> GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords,
string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "");
string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "");
/// <summary>
/// Gets a collection of <see cref="IMedia"/> objects by Parent Id
@@ -169,7 +183,7 @@ namespace Umbraco.Core.Services
/// <param name="filter">Search text filter</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
IEnumerable<IMedia> GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords,
string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "");
string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "");
/// <summary>
/// Gets a collection of <see cref="IMedia"/> objects by Parent Id
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
@@ -13,6 +14,15 @@ namespace Umbraco.Core.Services
/// </summary>
public interface IMemberService : IMembershipMemberService
{
/// <summary>
/// Gets all XML entries found in the cmsContentXml table
/// </summary>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of content items</returns>
IEnumerable<XElement> GetPagedXmlEntries(long pageIndex, int pageSize, out long totalRecords);
/// <summary>
/// Rebuilds all xml content in the cmsContentXml table for all documents
/// </summary>
+16 -11
View File
@@ -142,19 +142,24 @@ namespace Umbraco.Core.Services
/// <param name="userId">Optional Id of the user deleting the macro</param>
public void Save(IMacro macro, int userId = 0)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IMacro>(macro), this))
return;
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMacroRepository(uow))
{
repository.AddOrUpdate(macro);
uow.Commit();
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IMacro>(macro), this))
return;
Saved.RaiseEvent(new SaveEventArgs<IMacro>(macro, false), this);
}
if (string.IsNullOrWhiteSpace(macro.Name))
{
throw new ArgumentException("Cannot save macro with empty name.");
}
Audit(AuditType.Save, "Save Macro performed by user", userId, -1);
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMacroRepository(uow))
{
repository.AddOrUpdate(macro);
uow.Commit();
Saved.RaiseEvent(new SaveEventArgs<IMacro>(macro, false), this);
}
Audit(AuditType.Save, "Save Macro performed by user", userId, -1);
}
///// <summary>
+98 -47
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
@@ -450,7 +451,7 @@ namespace Umbraco.Core.Services
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public IEnumerable<IMedia> GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "")
public IEnumerable<IMedia> GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
{
long total;
var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter);
@@ -469,7 +470,7 @@ namespace Umbraco.Core.Services
/// <param name="orderDirection">Direction to order by</param>
/// <param name="filter">Search text filter</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable<IMedia> GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "")
public IEnumerable<IMedia> GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
{
return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter);
}
@@ -527,6 +528,10 @@ namespace Umbraco.Core.Services
/// <returns>An Enumerable flat list of <see cref="IMedia"/> objects</returns>
public IEnumerable<IMedia> GetDescendants(IMedia media)
{
//This is a check to ensure that the path is correct for this entity to avoid problems like: http://issues.umbraco.org/issue/U4-9336 due to data corruption
if (media.ValidatePath() == false)
throw new InvalidDataException(string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", media.Id, media.Path, media.ParentId));
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMediaRepository(uow))
{
@@ -619,7 +624,7 @@ namespace Umbraco.Core.Services
public IMedia GetMediaByPath(string mediaPath)
{
var umbracoFileValue = mediaPath;
const string Pattern = ".*[_][0-9]+[x][0-9]+[.].*";
var isResized = Regex.IsMatch(mediaPath, Pattern);
@@ -832,7 +837,11 @@ namespace Umbraco.Core.Services
{
return OperationStatus.Cancelled(evtMsgs);
}
}
if (string.IsNullOrWhiteSpace(media.Name))
{
throw new ArgumentException("Cannot save media with empty name.");
}
var uow = UowProvider.GetUnitOfWork();
@@ -998,56 +1007,63 @@ namespace Umbraco.Core.Services
{
if (media == null) throw new ArgumentNullException("media");
var originalPath = media.Path;
var evtMsgs = EventMessagesFactory.Get();
if (Trashing.IsRaisedEventCancelled(
new MoveEventArgs<IMedia>(new MoveEventInfo<IMedia>(media, originalPath, Constants.System.RecycleBinMedia)), this))
using (new WriteLock(Locker))
{
return OperationStatus.Cancelled(evtMsgs);
}
//Hack: this ensures that the entity's path is valid and if not it fixes/persists it
//see: http://issues.umbraco.org/issue/U4-9336
media.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate);
var moveInfo = new List<MoveEventInfo<IMedia>>
{
new MoveEventInfo<IMedia>(media, originalPath, Constants.System.RecycleBinMedia)
};
var originalPath = media.Path;
//Find Descendants, which will be moved to the recycle bin along with the parent/grandparent.
var descendants = GetDescendants(media).OrderBy(x => x.Level).ToList();
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMediaRepository(uow))
{
//TODO: This should be part of the repo!
//Remove 'published' xml from the cmsContentXml table for the unpublished media
uow.Database.Delete<ContentXmlDto>("WHERE nodeId = @Id", new { Id = media.Id });
media.ChangeTrashedState(true, Constants.System.RecycleBinMedia);
repository.AddOrUpdate(media);
//Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId
foreach (var descendant in descendants)
if (Trashing.IsRaisedEventCancelled(
new MoveEventArgs<IMedia>(new MoveEventInfo<IMedia>(media, originalPath, Constants.System.RecycleBinMedia)), this))
{
//Remove 'published' xml from the cmsContentXml table for the unpublished media
uow.Database.Delete<ContentXmlDto>("WHERE nodeId = @Id", new { Id = descendant.Id });
descendant.ChangeTrashedState(true, descendant.ParentId);
repository.AddOrUpdate(descendant);
moveInfo.Add(new MoveEventInfo<IMedia>(descendant, descendant.Path, descendant.ParentId));
return OperationStatus.Cancelled(evtMsgs);
}
uow.Commit();
var moveInfo = new List<MoveEventInfo<IMedia>>
{
new MoveEventInfo<IMedia>(media, originalPath, Constants.System.RecycleBinMedia)
};
//Find Descendants, which will be moved to the recycle bin along with the parent/grandparent.
var descendants = GetDescendants(media).OrderBy(x => x.Level).ToList();
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMediaRepository(uow))
{
//TODO: This should be part of the repo!
//Remove 'published' xml from the cmsContentXml table for the unpublished media
uow.Database.Delete<ContentXmlDto>("WHERE nodeId = @Id", new { Id = media.Id });
media.ChangeTrashedState(true, Constants.System.RecycleBinMedia);
repository.AddOrUpdate(media);
//Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId
foreach (var descendant in descendants)
{
//Remove 'published' xml from the cmsContentXml table for the unpublished media
uow.Database.Delete<ContentXmlDto>("WHERE nodeId = @Id", new { Id = descendant.Id });
descendant.ChangeTrashedState(true, descendant.ParentId);
repository.AddOrUpdate(descendant);
moveInfo.Add(new MoveEventInfo<IMedia>(descendant, descendant.Path, descendant.ParentId));
}
uow.Commit();
}
Trashed.RaiseEvent(
new MoveEventArgs<IMedia>(false, evtMsgs, moveInfo.ToArray()), this);
Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id);
return OperationStatus.Success(evtMsgs);
}
Trashed.RaiseEvent(
new MoveEventArgs<IMedia>(false, evtMsgs, moveInfo.ToArray()), this);
Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id);
return OperationStatus.Success(evtMsgs);
}
/// <summary>
@@ -1064,8 +1080,6 @@ namespace Umbraco.Core.Services
((IMediaServiceOperations)this).Delete(media, userId);
}
/// <summary>
/// Permanently deletes versions from an <see cref="IMedia"/> object prior to a specific date.
/// This method will never delete the latest version of a content item.
@@ -1154,7 +1168,6 @@ namespace Umbraco.Core.Services
public bool Sort(IEnumerable<IMedia> items, int userId = 0, bool raiseEvents = true)
{
var asArray = items.ToArray();
if (raiseEvents)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IMedia>(asArray), this))
@@ -1199,6 +1212,27 @@ namespace Umbraco.Core.Services
return true;
}
/// <summary>
/// Gets paged media descendants as XML by path
/// </summary>
/// <param name="path">Path starts with</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of media items</returns>
public IEnumerable<XElement> GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords)
{
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
Mandate.ParameterCondition(pageSize > 0, "pageSize");
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMediaRepository(uow))
{
var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, null, out totalRecords);
return contents;
}
}
/// <summary>
/// Rebuilds all xml content in the cmsContentXml table for all media
/// </summary>
@@ -1295,6 +1329,23 @@ namespace Umbraco.Core.Services
}
}
/// <summary>
/// Hack: This is used to fix some data if an entity's properties are invalid/corrupt
/// </summary>
/// <param name="media"></param>
private void QuickUpdate(IMedia media)
{
if (media == null) throw new ArgumentNullException("media");
if (media.HasIdentity == false) throw new InvalidOperationException("Cannot update an entity without an Identity");
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMediaRepository(uow))
{
repository.AddOrUpdate(media);
uow.Commit();
}
}
#region Event Handlers
/// <summary>
@@ -612,6 +612,26 @@ namespace Umbraco.Core.Services
Audit(AuditType.Publish, "MemberService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1);
}
/// <summary>
/// Gets paged member descendants as XML by path
/// </summary>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of member items</returns>
public IEnumerable<XElement> GetPagedXmlEntries(long pageIndex, int pageSize, out long totalRecords)
{
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
Mandate.ParameterCondition(pageSize > 0, "pageSize");
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMemberRepository(uow))
{
var contents = repository.GetPagedXmlEntriesByPath("-1", pageIndex, pageSize, null, out totalRecords);
return contents;
}
}
#endregion
#region IMembershipMemberService Implementation
@@ -978,6 +998,11 @@ namespace Umbraco.Core.Services
}
}
if (string.IsNullOrWhiteSpace(entity.Name))
{
throw new ArgumentException("Cannot save member with empty name.");
}
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMemberRepository(uow))
{

Some files were not shown because too many files have changed in this diff Show More