Compare commits

...

204 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
Chris Houston 90136c524c Small English grammar error. 2016-12-06 11:05:59 +00: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
Anders Bjerner bc8b8a69b8 Typo (sorta) 2016-11-21 10:26:08 +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
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
Alexander Bryukhov 87814fa43c Update UI lang file for ru-ru
All latest 7.5.5 changes
2016-11-12 12:12:41 +07: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
Tom Pipe 0a49d54a85 Fixed issue with member properties sort order 2016-10-29 18:23:25 +01: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
191 changed files with 5490 additions and 2209 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
Umbraco CMS
===========
The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 350,000 websites worldwide: [https://umbraco.com](https://umbraco.com)
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/)
+6 -6
View File
@@ -28,17 +28,17 @@
<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.2, 2.0.0)" />
<dependency id="ClientDependency-Mvc5" version="[1.8.0, 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>
+3
View File
@@ -18,6 +18,7 @@
<dependency id="UmbracoCms.Core" version="[$version$]" />
<dependency id="Newtonsoft.Json" version="[6.0.8, 10.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>
@@ -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.6
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" />
+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.6")]
[assembly: AssemblyInformationalVersion("7.5.6")]
[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.6");
private static readonly Version Version = new Version("7.5.10");
/// <summary>
/// Gets the current version of Umbraco.
+1 -1
View File
@@ -415,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.");
}
}
+29 -13
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>
@@ -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.
+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
@@ -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
///
@@ -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
@@ -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)
{
@@ -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>();
@@ -27,6 +27,7 @@ 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 PocoToSqlExpressionVisitor<T>();
@@ -34,6 +35,13 @@ namespace Umbraco.Core.Persistence
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());
}
private static string GetFieldName<T>(Expression<Func<T, object>> fieldSelector, ISqlSyntaxProvider sqlSyntax)
{
var field = ExpressionHelper.FindProperty(fieldSelector) as PropertyInfo;
@@ -20,6 +20,7 @@ namespace Umbraco.Core.Persistence.Querying
_mapper = mapper;
}
[Obsolete("Use the overload the specifies a SqlSyntaxProvider")]
public ModelToSqlExpressionVisitor()
: this(SqlSyntaxContext.SqlSyntaxProvider, MappingResolver.Current.ResolveMapperByType(typeof(T)))
{ }
@@ -14,12 +14,19 @@ namespace Umbraco.Core.Persistence.Querying
{
private readonly Database.PocoData _pd;
public PocoToSqlExpressionVisitor()
: base(SqlSyntaxContext.SqlSyntaxProvider)
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 &&
@@ -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,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Xml;
@@ -53,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();
@@ -70,69 +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 });
}
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
#region Static Queries
private readonly IQuery<IContent> _publishedQuery = Query<IContent>.Builder.Where(x => x.Published == true);
#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";
@@ -179,20 +213,32 @@ namespace Umbraco.Core.Persistence.Repositories
// 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();
Func<int, Sql, Sql> translate = (bId, sql) =>
{
if (contentTypeIdsA.Length > 0)
{
sql.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
}
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 query = GetBaseQuery(false);
if (contentTypeIdsA.Length > 0)
query = query
.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
query = query
.Where<NodeDto>(x => x.NodeId > baseId && x.Trashed == false)
.Where<DocumentDto>(x => x.Published)
.OrderBy<NodeDto>(x => x.NodeId, SqlSyntax);
var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize))
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();
@@ -212,23 +258,29 @@ namespace Umbraco.Core.Persistence.Repositories
Logger.Error<MediaRepository>("Could not rebuild XML for nodeId=" + xmlItem.NodeId, e);
}
}
baseId = xmlItems.Last().NodeId;
baseId = xmlItems[xmlItems.Count - 1].NodeId;
}
}
public override IEnumerable<IContent> GetAllVersions(int id)
{
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { Id = id })
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
return ProcessQuery(sql, true);
Func<Sql, Sql> translate = s =>
{
return s.Where(GetBaseWhereClause(), new {Id = id})
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
};
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();
@@ -244,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;
@@ -348,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
@@ -485,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
@@ -620,21 +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);
return ProcessQuery(sql, true);
return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)), true);
}
/// <summary>
/// This builds the Xml document used for the XML cache
/// </summary>
@@ -666,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;
@@ -720,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>
@@ -806,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"),
sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField,
(sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
@@ -838,26 +897,80 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
return base.GetDatabaseFieldNameForOrderBy(orderBy);
}
private IEnumerable<IContent> ProcessQuery(Sql sql, bool withCache = false)
/// <summary>
/// This is the underlying method that processes most queries for this repository
/// </summary>
/// <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 IEnumerable<IContent> 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<DocumentDto, ContentVersionDto, ContentDto, NodeDto, DocumentPublishedReadOnlyDto>(sql);
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.
//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 "))
{
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)
{
//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);
}
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));
if (cached != null && cached.Published)
//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;
@@ -866,9 +979,19 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
// else, need to fetch from the database
// content type repository is full-cache so OK to get each one independently
var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId);
content[i] = factory.BuildEntity(dto);
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)
@@ -889,7 +1012,7 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
.ToDictionary(x => x.Id, x => x);
// load all properties for all documents from database in 1 query
var propertyData = GetPropertyCollection(sql, defs);
var propertyData = GetPropertyCollection(pagingSqlQuery, defs);
// assign
var dtoIndex = 0;
@@ -909,7 +1032,7 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((Entity) cc).ResetDirtyProperties(false);
cc.ResetDirtyProperties(false);
}
return content;
@@ -926,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)
@@ -978,7 +1100,7 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
return currentName;
}
/// <summary>
/// Dispose disposable properties
/// </summary>
@@ -993,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,15 +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 = "");
/// <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>
IEnumerable<XElement> GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords);
}
}
@@ -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);
}
}
@@ -68,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)
@@ -76,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()
@@ -138,16 +143,32 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { Id = id })
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
return ProcessQuery(sql, true);
return ProcessQuery(sql, new PagingSqlQuery(sql), true);
}
private IEnumerable<IMedia> ProcessQuery(Sql sql, bool withCache = false)
/// <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>(sql);
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];
@@ -165,9 +186,19 @@ namespace Umbraco.Core.Persistence.Repositories
// else, need to fetch from the database
// content type repository is full-cache so OK to get each one independently
var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId);
var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId);
content[i] = factory.BuildEntity(dto);
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(
@@ -180,7 +211,7 @@ namespace Umbraco.Core.Persistence.Repositories
}
// load all properties for all documents from database in 1 query
var propertyData = GetPropertyCollection(sql, defs);
var propertyData = GetPropertyCollection(pagingSqlQuery, defs);
// assign
var dtoIndex = 0;
@@ -195,7 +226,7 @@ namespace Umbraco.Core.Persistence.Repositories
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((Entity) cc).ResetDirtyProperties(false);
cc.ResetDirtyProperties(false);
}
return content;
@@ -205,26 +236,16 @@ namespace Umbraco.Core.Persistence.Repositories
{
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 = 200, IEnumerable<int> contentTypeIds = null)
@@ -245,9 +266,10 @@ namespace Umbraco.Core.Persistence.Repositories
query = query
.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
query = query
.Where<NodeDto>(x => x.NodeId > baseId)
.Where<NodeDto>(x => x.NodeId > baseId, SqlSyntax)
.OrderBy<NodeDto>(x => x.NodeId, SqlSyntax);
var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize))
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();
@@ -324,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
@@ -397,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
@@ -493,54 +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"),
sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField,
(sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
/// <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> GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords)
{
Sql query;
if (path == "-1")
{
query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE nodeObjectType = @0)", Guid.Parse(Constants.ObjectTypes.Media)).OrderBy("nodeId");
}
else
{
query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE path LIKE @0)", path.EnsureEndsWith(",%")).OrderBy("nodeId");
}
var pagedResult = Database.Page<ContentXmlDto>(pageIndex+1, pageSize, query);
totalRecords = pagedResult.TotalItems;
return pagedResult.Items.Select(dto => XElement.Parse(dto.Xml));
}
/// <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];
@@ -68,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));
}
@@ -90,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
{
@@ -98,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));
}
}
@@ -107,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,8 +385,8 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { Id = id })
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
return ProcessQuery(sql, true);
}
return ProcessQuery(sql, new PagingSqlQuery(sql), true);
}
public void RebuildXmlStructures(Func<IMember, XElement> serializer, int groupSize = 200, IEnumerable<int> contentTypeIds = null)
{
@@ -403,7 +408,8 @@ namespace Umbraco.Core.Persistence.Repositories
query = query
.Where<NodeDto>(x => x.NodeId > baseId)
.OrderBy<NodeDto>(x => x.NodeId, SqlSyntax);
var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize))
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();
@@ -444,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];
@@ -535,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));
}
@@ -617,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"),
sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField,
(sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
@@ -659,10 +665,21 @@ namespace Umbraco.Core.Persistence.Repositories
return base.GetEntityPropertyNameForOrderBy(orderBy);
}
private IEnumerable<IMember> ProcessQuery(Sql sql, bool withCache = false)
/// <summary>
/// This is the underlying method that processes most queries for this repository
/// </summary>
/// <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 IEnumerable<IMember> 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<MemberDto, ContentVersionDto, ContentDto, NodeDto>(sql);
var dtos = Database.Fetch<MemberDto, ContentVersionDto, ContentDto, NodeDto>(sqlFull);
var content = new IMember[dtos.Count];
var defs = new List<DocumentDefinition>();
@@ -699,7 +716,7 @@ namespace Umbraco.Core.Persistence.Repositories
}
// load all properties for all documents from database in 1 query
var propertyData = GetPropertyCollection(sql, defs);
var propertyData = GetPropertyCollection(pagingSqlQuery, defs);
// assign
var dtoIndex = 0;
@@ -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);
}
@@ -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);
}
}
}
}
@@ -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;
+75 -7
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;
@@ -58,7 +60,7 @@ namespace Umbraco.Core.Services
#region Static Queries
private readonly IQuery<IContent> _notTrashedQuery = Query<IContent>.Builder.Where(x => x.Trashed == false);
private IQuery<IContent> _notTrashedQuery;
#endregion
@@ -583,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);
@@ -602,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);
}
@@ -714,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);
@@ -793,6 +800,12 @@ 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()))
{
return repository.GetByPublishedVersion(_notTrashedQuery);
@@ -990,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(
@@ -1677,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();
@@ -1747,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>
@@ -1806,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();
@@ -1913,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
@@ -2154,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();
@@ -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))
{
+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))
+7 -1
View File
@@ -64,7 +64,7 @@ namespace Umbraco.Core.Services
#region Static Queries
private readonly IQuery<IUmbracoEntity> _rootEntityQuery = Query<IUmbracoEntity>.Builder.Where(x => x.ParentId == -1);
private IQuery<IUmbracoEntity> _rootEntityQuery;
#endregion
@@ -392,6 +392,12 @@ 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()))
{
+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
+2 -2
View File
@@ -169,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
@@ -183,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>
+78 -48
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))
@@ -1215,7 +1228,7 @@ namespace Umbraco.Core.Services
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMediaRepository(uow))
{
var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, out totalRecords);
var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, null, out totalRecords);
return contents;
}
}
@@ -1316,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))
{
@@ -78,6 +78,11 @@ namespace Umbraco.Core.Services
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IMemberType>(memberType), this))
return;
if (string.IsNullOrWhiteSpace(memberType.Name))
{
throw new ArgumentException("Cannot save MemberType with empty name.");
}
using (new WriteLock(Locker))
{
var uow = UowProvider.GetUnitOfWork();
+22
View File
@@ -125,6 +125,11 @@ namespace Umbraco.Core.Services
{
if (userType == null) throw new ArgumentNullException("userType");
if (string.IsNullOrWhiteSpace(username))
{
throw new ArgumentException("Cannot create user with empty username.");
}
//TODO: PUT lock here!!
var uow = UowProvider.GetUnitOfWork();
@@ -312,6 +317,15 @@ namespace Umbraco.Core.Services
return;
}
if (string.IsNullOrWhiteSpace(entity.Username))
{
throw new ArgumentException("Cannot save user with empty username.");
}
if (string.IsNullOrWhiteSpace(entity.Name))
{
throw new ArgumentException("Cannot save user with empty name.");
}
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateUserRepository(uow))
{
@@ -353,6 +367,14 @@ namespace Umbraco.Core.Services
{
foreach (var member in entities)
{
if (string.IsNullOrWhiteSpace(member.Username))
{
throw new ArgumentException("Cannot save user with empty username.");
}
if (string.IsNullOrWhiteSpace(member.Name))
{
throw new ArgumentException("Cannot save user with empty name.");
}
repository.AddOrUpdate(member);
}
//commit the whole lot in one go
+11 -25
View File
@@ -21,7 +21,7 @@
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
@@ -39,67 +39,55 @@
<ItemGroup>
<Reference Include="AutoMapper, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
<HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="AutoMapper.Net4, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
<HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="HtmlAgilityPack, Version=1.4.9.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll</HintPath>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib">
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="ImageProcessor, Version=2.4.5.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ImageProcessor.2.4.5.0\lib\net45\ImageProcessor.dll</HintPath>
<Reference Include="ImageProcessor, Version=2.5.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ImageProcessor.2.5.2\lib\net45\ImageProcessor.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="log4net, Version=1.2.11.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\log4net-mediumtrust.2.0.0\lib\log4net.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNet.Identity.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNet.Identity.Owin, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.Identity.Owin.2.2.1\lib\net45\Microsoft.AspNet.Identity.Owin.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Security">
<Reference Include="Microsoft.Owin.Security, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Security.Cookies, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Owin.Security.Cookies.3.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Security.OAuth, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Owin.Security.OAuth.3.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll</HintPath>
</Reference>
<Reference Include="MiniProfiler, Version=2.1.0.0, Culture=neutral, PublicKeyToken=b44f9351044011a3, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll</HintPath>
</Reference>
<Reference Include="MySql.Data">
<SpecificVersion>False</SpecificVersion>
<Reference Include="MySql.Data, Version=6.9.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d, processorArchitecture=MSIL">
<HintPath>..\packages\MySql.Data.6.9.8\lib\net45\MySql.Data.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Owin">
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference>
<Reference Include="Semver">
<Reference Include="Semver, Version=1.1.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\semver.1.1.2\lib\net45\Semver.dll</HintPath>
</Reference>
<Reference Include="System" />
@@ -107,11 +95,9 @@
<Reference Include="System.Configuration" />
<Reference Include="System.Data.Entity" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.dll</HintPath>
</Reference>
<Reference Include="System.Data.SqlServerCe.Entity, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll</HintPath>
</Reference>
<Reference Include="System.DirectoryServices.AccountManagement" />
@@ -296,6 +282,7 @@
<Compile Include="Configuration\UmbracoSettings\ServerCollection.cs" />
<Compile Include="Configuration\UmbracoSettings\ServerElement.cs" />
<Compile Include="Configuration\UmbracoSettings\TemplatesElement.cs" />
<Compile Include="Configuration\UmbracoSettings\UmbracoConfigurationElement.cs" />
<Compile Include="Configuration\UmbracoSettings\UmbracoSettingsSection.cs" />
<Compile Include="Configuration\UmbracoSettings\UrlReplacingElement.cs" />
<Compile Include="Configuration\UmbracoSettings\UserProviderElement.cs" />
@@ -480,6 +467,7 @@
<Compile Include="Persistence\RecordPersistenceType.cs" />
<Compile Include="Persistence\Relators\AccessRulesRelator.cs" />
<Compile Include="Persistence\Repositories\AuditRepository.cs" />
<Compile Include="Persistence\Repositories\BaseQueryType.cs" />
<Compile Include="Persistence\Repositories\EntityContainerRepository.cs" />
<Compile Include="Persistence\Repositories\DomainRepository.cs" />
<Compile Include="Persistence\Repositories\ExternalLoginRepository.cs" />
@@ -1440,9 +1428,7 @@
<ItemGroup>
<None Include="app.config" />
<None Include="FileResources\BlockingWeb.config" />
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\umbraco.interfaces\umbraco.interfaces.csproj">
+3 -3
View File
@@ -2,8 +2,8 @@
<packages>
<package id="AutoMapper" version="3.3.1" targetFramework="net45" />
<package id="HtmlAgilityPack" version="1.4.9" targetFramework="net45" />
<package id="ImageProcessor" version="2.4.5.0" targetFramework="net45" />
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net4" />
<package id="ImageProcessor" version="2.5.2" targetFramework="net45" />
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net45" />
<package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net45" />
<package id="Microsoft.AspNet.Identity.Owin" version="2.2.1" targetFramework="net45" />
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" />
@@ -15,6 +15,6 @@
<package id="Newtonsoft.Json" version="6.0.8" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" />
<package id="semver" version="1.1.2" targetFramework="net45" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net4" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net45" />
<package id="SqlServerCE" version="4.0.0.1" targetFramework="net45" />
</packages>
@@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnostics.Windows;
namespace Umbraco.Tests.Benchmarks
{
/// <summary>
/// Want to check what is faster OfType or Cast when a enurable all has the same items
/// </summary>
[Config(typeof(Config))]
public class LinqCastBenchmarks
{
private class Config : ManualConfig
{
public Config()
{
Add(new MemoryDiagnoser());
}
}
public LinqCastBenchmarks()
{
_array = new List<object>();
_array.AddRange(Enumerable.Range(0, 10000).Select(x => x.ToString()));
}
private readonly List<object> _array;
[Benchmark(Baseline = true)]
public void OfType()
{
foreach (var i in _array.OfType<string>())
{
var a = i;
}
}
[Benchmark()]
public void Cast()
{
foreach (var i in _array.Cast<string>())
{
var a = i;
}
}
[Benchmark()]
public void ExplicitCast()
{
foreach (var i in _array)
{
var a = (string)i;
}
}
}
}
+10 -16
View File
@@ -8,23 +8,17 @@ namespace Umbraco.Tests.Benchmarks
{
public static void Main(string[] args)
{
if (args.Length == 1)
var switcher = new BenchmarkSwitcher(new[]
{
var type = Assembly.GetExecutingAssembly().GetType("Umbraco.Tests.Benchmarks." +args[0]);
if (type == null)
{
Console.WriteLine("Unknown benchmark.");
}
else
{
var summary = BenchmarkRunner.Run(type);
Console.ReadLine();
}
}
else
{
Console.WriteLine("?");
}
typeof(BulkInsertBenchmarks),
typeof(ModelToSqlExpressionHelperBenchmarks),
typeof(XmlBenchmarks),
typeof(LinqCastBenchmarks),
//typeof(DeepCloneBenchmarks),
typeof(XmlPublishedContentInitBenchmarks),
});
switcher.Run(args);
}
}
}
@@ -39,31 +39,24 @@
<ItemGroup>
<Reference Include="BenchmarkDotNet, Version=0.9.9.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
<HintPath>..\packages\BenchmarkDotNet.0.9.9\lib\net45\BenchmarkDotNet.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="BenchmarkDotNet.Core, Version=0.9.9.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
<HintPath>..\packages\BenchmarkDotNet.Core.0.9.9\lib\net45\BenchmarkDotNet.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="BenchmarkDotNet.Diagnostics.Windows, Version=0.9.9.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
<HintPath>..\packages\BenchmarkDotNet.Diagnostics.Windows.0.9.9\lib\net45\BenchmarkDotNet.Diagnostics.Windows.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="BenchmarkDotNet.Toolchains.Roslyn, Version=0.9.9.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
<HintPath>..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.9.9\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.CodeAnalysis, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.CodeAnalysis.CSharp, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Diagnostics.Tracing.TraceEvent, Version=1.0.41.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.1.37.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
@@ -73,20 +66,16 @@
<Reference Include="System.Core" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Data.SqlServerCe.Entity, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Management" />
<Reference Include="System.Reflection.Metadata, Version=1.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reflection.Metadata.1.2.0\lib\portable-net45+win8\System.Reflection.Metadata.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.0.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
@@ -96,20 +85,18 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="LinqCastBenchmarks.cs" />
<Compile Include="ModelToSqlExpressionHelperBenchmarks.cs" />
<Compile Include="BulkInsertBenchmarks.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="XmlBenchmarks.cs" />
<Compile Include="XmlPublishedContentInitBenchmarks.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj">
<Project>{31785bc3-256c-4613-b2f5-a1b0bdded8c1}</Project>
@@ -119,8 +106,27 @@
<Project>{5d3b8245-ada6-453f-a008-50ed04bfe770}</Project>
<Name>Umbraco.Tests</Name>
</ProjectReference>
<ProjectReference Include="..\Umbraco.Web\Umbraco.Web.csproj">
<Project>{651e1350-91b6-44b7-bd60-7207006d7003}</Project>
<Name>Umbraco.Web</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>
if not exist "$(TargetDir)x86" md "$(TargetDir)x86"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8854.1\NativeBinaries\x86\*.*" "$(TargetDir)x86"
if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8854.1\NativeBinaries\amd64\*.*" "$(TargetDir)amd64"</PostBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PreBuildEvent>xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\amd64\*.* "$(TargetDir)amd64\" /Y /F /E /I /C /D
xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" /Y /F /E /I /C /D</PreBuildEvent>
</PropertyGroup>
<Import Project="..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets" Condition="Exists('..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -128,14 +134,6 @@
</PropertyGroup>
<Error Condition="!Exists('..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets'))" />
</Target>
<PropertyGroup>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PreBuildEvent>xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\amd64\*.* "$(TargetDir)amd64\" /Y /F /E /I /C /D
xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" /Y /F /E /I /C /D</PreBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
@@ -0,0 +1,315 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnostics.Windows;
using BenchmarkDotNet.Jobs;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.PublishedCache.XmlPublishedCache;
namespace Umbraco.Tests.Benchmarks
{
[Config(typeof(Config))]
public class XmlPublishedContentInitBenchmarks
{
private class Config : ManualConfig
{
public Config()
{
Add(new MemoryDiagnoser());
//The 'quick and dirty' settings, so it runs a little quicker
// see benchmarkdotnet FAQ
Add(Job.Default
.WithLaunchCount(1) // benchmark process will be launched only once
.WithIterationTime(100) // 100ms per iteration
.WithWarmupCount(3) // 3 warmup iteration
.WithTargetCount(3)); // 3 target iteration
}
}
public XmlPublishedContentInitBenchmarks()
{
_xml10 = Build(10);
_xml100 = Build(100);
_xml1000 = Build(1000);
_xml10000 = Build(10000);
}
private readonly string[] _intAttributes = { "id", "parentID", "nodeType", "level", "writerID", "creatorID", "template", "sortOrder", "isDoc", "isDraft" };
private readonly string[] _strAttributes = { "nodeName", "urlName", "writerName", "creatorName", "path" };
private readonly string[] _dateAttributes = { "createDate", "updateDate" };
private readonly string[] _guidAttributes = { "key", "version" };
private XmlDocument Build(int children)
{
var xml = new XmlDocument();
var root = Build(xml, "Home", 10);
for (int i = 0; i < children; i++)
{
var child = Build(xml, "child" + i, 10);
root.AppendChild(child);
}
xml.AppendChild(root);
return xml;
}
private XmlElement Build(XmlDocument xml, string name, int propertyCount)
{
var random = new Random();
var content = xml.CreateElement(name);
foreach (var p in _intAttributes)
{
var a = xml.CreateAttribute(p);
a.Value = random.Next(1, 9).ToInvariantString();
content.Attributes.Append(a);
}
foreach (var p in _strAttributes)
{
var a = xml.CreateAttribute(p);
a.Value = Guid.NewGuid().ToString();
content.Attributes.Append(a);
}
foreach (var p in _guidAttributes)
{
var a = xml.CreateAttribute(p);
a.Value = Guid.NewGuid().ToString();
content.Attributes.Append(a);
}
foreach (var p in _dateAttributes)
{
var a = xml.CreateAttribute(p);
a.Value = DateTime.Now.ToString("o");
content.Attributes.Append(a);
}
for (int i = 0; i < propertyCount; i++)
{
var prop = xml.CreateElement("prop" + i);
var cdata = xml.CreateCDataSection(string.Join("", Enumerable.Range(0, 10).Select(x => Guid.NewGuid().ToString())));
prop.AppendChild(cdata);
content.AppendChild(prop);
}
return content;
}
private readonly XmlDocument _xml10;
private readonly XmlDocument _xml100;
private readonly XmlDocument _xml1000;
private readonly XmlDocument _xml10000;
//out props
int id, nodeType, level, writerId, creatorId, template, sortOrder;
Guid key, version;
string name, urlName, writerName, creatorName, docTypeAlias, path;
bool isDraft;
DateTime createDate, updateDate;
PublishedContentType publishedContentType;
Dictionary<string, IPublishedProperty> properties;
[Benchmark(Baseline = true, OperationsPerInvoke = 10)]
public void Original_10_Children()
{
OriginalInitializeNode(_xml10.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties);
}
[Benchmark(OperationsPerInvoke = 10)]
public void Original_100_Children()
{
OriginalInitializeNode(_xml100.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties);
}
[Benchmark(OperationsPerInvoke = 10)]
public void Original_1000_Children()
{
OriginalInitializeNode(_xml1000.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties);
}
[Benchmark(OperationsPerInvoke = 10)]
public void Original_10000_Children()
{
OriginalInitializeNode(_xml10000.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties);
}
[Benchmark(OperationsPerInvoke = 10)]
public void Enhanced_10_Children()
{
XmlPublishedContent.InitializeNode(_xml10.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties, GetPublishedContentType);
}
[Benchmark(OperationsPerInvoke = 10)]
public void Enhanced_100_Children()
{
XmlPublishedContent.InitializeNode(_xml100.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties, GetPublishedContentType);
}
[Benchmark(OperationsPerInvoke = 10)]
public void Enhanced_1000_Children()
{
XmlPublishedContent.InitializeNode(_xml1000.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties, GetPublishedContentType);
}
[Benchmark(OperationsPerInvoke = 10)]
public void Enhanced_10000_Children()
{
XmlPublishedContent.InitializeNode(_xml10000.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties, GetPublishedContentType);
}
internal static void OriginalInitializeNode(XmlNode xmlNode, bool legacy, bool isPreviewing,
out int id, out Guid key, out int template, out int sortOrder, out string name, out string writerName, out string urlName,
out string creatorName, out int creatorId, out int writerId, out string docTypeAlias, out int docTypeId, out string path,
out Guid version, out DateTime createDate, out DateTime updateDate, out int level, out bool isDraft,
out PublishedContentType contentType, out Dictionary<string, IPublishedProperty> properties)
{
//initialize the out params with defaults:
writerName = null;
docTypeAlias = null;
id = template = sortOrder = template = creatorId = writerId = docTypeId = level = default(int);
key = version = default(Guid);
name = writerName = urlName = creatorName = docTypeAlias = path = null;
createDate = updateDate = default(DateTime);
isDraft = false;
contentType = null;
properties = null;
//return if this is null
if (xmlNode == null)
{
return;
}
if (xmlNode.Attributes != null)
{
id = int.Parse(xmlNode.Attributes.GetNamedItem("id").Value);
if (xmlNode.Attributes.GetNamedItem("key") != null) // because, migration
key = Guid.Parse(xmlNode.Attributes.GetNamedItem("key").Value);
if (xmlNode.Attributes.GetNamedItem("template") != null)
template = int.Parse(xmlNode.Attributes.GetNamedItem("template").Value);
if (xmlNode.Attributes.GetNamedItem("sortOrder") != null)
sortOrder = int.Parse(xmlNode.Attributes.GetNamedItem("sortOrder").Value);
if (xmlNode.Attributes.GetNamedItem("nodeName") != null)
name = xmlNode.Attributes.GetNamedItem("nodeName").Value;
if (xmlNode.Attributes.GetNamedItem("writerName") != null)
writerName = xmlNode.Attributes.GetNamedItem("writerName").Value;
if (xmlNode.Attributes.GetNamedItem("urlName") != null)
urlName = xmlNode.Attributes.GetNamedItem("urlName").Value;
// Creatorname is new in 2.1, so published xml might not have it!
try
{
creatorName = xmlNode.Attributes.GetNamedItem("creatorName").Value;
}
catch
{
creatorName = writerName;
}
//Added the actual userID, as a user cannot be looked up via full name only...
if (xmlNode.Attributes.GetNamedItem("creatorID") != null)
creatorId = int.Parse(xmlNode.Attributes.GetNamedItem("creatorID").Value);
if (xmlNode.Attributes.GetNamedItem("writerID") != null)
writerId = int.Parse(xmlNode.Attributes.GetNamedItem("writerID").Value);
if (legacy)
{
if (xmlNode.Attributes.GetNamedItem("nodeTypeAlias") != null)
docTypeAlias = xmlNode.Attributes.GetNamedItem("nodeTypeAlias").Value;
}
else
{
docTypeAlias = xmlNode.Name;
}
if (xmlNode.Attributes.GetNamedItem("nodeType") != null)
docTypeId = int.Parse(xmlNode.Attributes.GetNamedItem("nodeType").Value);
if (xmlNode.Attributes.GetNamedItem("path") != null)
path = xmlNode.Attributes.GetNamedItem("path").Value;
if (xmlNode.Attributes.GetNamedItem("version") != null)
version = new Guid(xmlNode.Attributes.GetNamedItem("version").Value);
if (xmlNode.Attributes.GetNamedItem("createDate") != null)
createDate = DateTime.Parse(xmlNode.Attributes.GetNamedItem("createDate").Value);
if (xmlNode.Attributes.GetNamedItem("updateDate") != null)
updateDate = DateTime.Parse(xmlNode.Attributes.GetNamedItem("updateDate").Value);
if (xmlNode.Attributes.GetNamedItem("level") != null)
level = int.Parse(xmlNode.Attributes.GetNamedItem("level").Value);
isDraft = (xmlNode.Attributes.GetNamedItem("isDraft") != null);
}
// load data
var dataXPath = legacy ? "data" : "* [not(@isDoc)]";
var nodes = xmlNode.SelectNodes(dataXPath);
contentType = GetPublishedContentType(PublishedItemType.Content, docTypeAlias);
var propertyNodes = new Dictionary<string, XmlNode>();
if (nodes != null)
foreach (XmlNode n in nodes)
{
var attrs = n.Attributes;
if (attrs == null) continue;
var alias = legacy
? attrs.GetNamedItem("alias").Value
: n.Name;
propertyNodes[alias.ToLowerInvariant()] = n;
}
properties = contentType.PropertyTypes.Select(p =>
{
XmlNode n;
return propertyNodes.TryGetValue(p.PropertyTypeAlias.ToLowerInvariant(), out n)
? new XmlPublishedProperty(p, isPreviewing, n)
: new XmlPublishedProperty(p, isPreviewing);
}).Cast<IPublishedProperty>().ToDictionary(
x => x.PropertyTypeAlias,
x => x,
StringComparer.OrdinalIgnoreCase);
}
private static PublishedContentType GetPublishedContentType(PublishedItemType type, string alias)
{
return new PublishedContentType(alias, new string[] {},
new List<PublishedPropertyType>(Enumerable.Range(0, 10).Select(x => new PublishedPropertyType("prop" + x, 0, "test", initConverters:false))));
}
}
}
+190
View File
@@ -0,0 +1,190 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using NUnit.Framework;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Dependencies
{
[TestFixture]
public class NuGet
{
[Test]
public void NuGet_Package_Versions_Are_The_Same_In_All_Package_Config_Files()
{
var packagesAndVersions = GetNuGetPackagesInSolution();
Assert.IsTrue(packagesAndVersions.Any());
var failTest = false;
foreach (var package in packagesAndVersions.OrderBy(x => x.ConfigFilePath))
{
var matchingPackages = packagesAndVersions.Where(x => string.Equals(x.PackageName, package.PackageName, StringComparison.InvariantCultureIgnoreCase));
foreach (var matchingPackage in matchingPackages)
{
if (package.PackageVersion == matchingPackage.PackageVersion)
continue;
Debug.WriteLine("Package '{0}' with version {1} in {2} doesn't match with version {3} in {4}",
package.PackageName, package.PackageVersion, package.ConfigFilePath,
matchingPackage.PackageVersion, matchingPackage.ConfigFilePath);
failTest = true;
}
}
Assert.IsFalse(failTest);
}
[Test]
public void NuSpec_File_Matches_Installed_Dependencies()
{
var solutionDirectory = GetSolutionDirectory();
Assert.NotNull(solutionDirectory);
Assert.NotNull(solutionDirectory.Parent);
var nuSpecPath = solutionDirectory.Parent.FullName + "\\build\\NuSpecs\\";
Assert.IsTrue(Directory.Exists(nuSpecPath));
var packagesAndVersions = GetNuGetPackagesInSolution();
var failTest = false;
var nuSpecFiles = Directory.GetFiles(nuSpecPath);
foreach (var fileName in nuSpecFiles.Where(x => x.EndsWith("nuspec")))
{
var serializer = new XmlSerializer(typeof(NuSpec));
using (var reader = new StreamReader(fileName))
{
var nuspec = (NuSpec)serializer.Deserialize(reader);
var dependencies = nuspec.MetaData.dependencies;
//UmbracoCms.Core has version "[$version$]" - ignore
foreach (var dependency in dependencies.Where(x => x.Id != "UmbracoCms.Core"))
{
var dependencyVersionRange = dependency.Version.Replace("[", string.Empty).Replace("(", string.Empty).Split(',');
var dependencyMinimumVersion = dependencyVersionRange.First().Trim();
var matchingPackages = packagesAndVersions.Where(x => string.Equals(x.PackageName, dependency.Id,
StringComparison.InvariantCultureIgnoreCase)).ToList();
if (matchingPackages.Any() == false)
continue;
// NuGet_Package_Versions_Are_The_Same_In_All_Package_Config_Files test
// guarantees that all packages have one version, solutionwide, so it's okay to take First() here
if (dependencyMinimumVersion != matchingPackages.First().PackageVersion)
{
Debug.WriteLine("NuSpec dependency '{0}' with minimum version {1} in doesn't match with version {2} in the solution",
dependency.Id, dependencyMinimumVersion, matchingPackages.First().PackageVersion);
failTest = true;
}
}
}
}
Assert.IsFalse(failTest);
}
private List<PackageVersionMatcher> GetNuGetPackagesInSolution()
{
var packagesAndVersions = new List<PackageVersionMatcher>();
var solutionDirectory = GetSolutionDirectory();
if (solutionDirectory == null)
return packagesAndVersions;
var packageConfigFiles = new List<FileInfo>();
var packageDirectories =
solutionDirectory.GetDirectories().Where(x =>
x.Name.StartsWith("Umbraco.Tests") == false &&
x.Name.StartsWith("Umbraco.MSBuild.Tasks") == false).ToArray();
foreach (var directory in packageDirectories)
{
var packagesFiles = directory.EnumerateFiles("packages.config");
packageConfigFiles.AddRange(packagesFiles);
}
foreach (var file in packageConfigFiles)
{
//read all and de-duplicate packages
var serializer = new XmlSerializer(typeof(PackageConfigEntries));
using (var reader = new StreamReader(file.FullName))
{
var packages = (PackageConfigEntries)serializer.Deserialize(reader);
packagesAndVersions.AddRange(packages.Package.Select(package =>
new PackageVersionMatcher
{
PackageName = package.Id,
PackageVersion = package.Version,
ConfigFilePath = file.FullName
}));
}
}
return packagesAndVersions;
}
private DirectoryInfo GetSolutionDirectory()
{
var testsDirectory = new FileInfo(TestHelper.MapPathForTest("~/"));
if (testsDirectory.Directory == null)
return null;
if (testsDirectory.Directory.Parent == null || testsDirectory.Directory.Parent.Parent == null)
return null;
var solutionDirectory = testsDirectory.Directory.Parent.Parent.Parent;
return solutionDirectory;
}
}
public class PackageVersionMatcher
{
public string ConfigFilePath { get; set; }
public string PackageName { get; set; }
public string PackageVersion { get; set; }
}
[XmlType(AnonymousType = true)]
[XmlRoot(Namespace = "", IsNullable = false, ElementName = "packages")]
public class PackageConfigEntries
{
[XmlElement("package")]
public PackagesPackage[] Package { get; set; }
}
[XmlType(AnonymousType = true, TypeName = "package")]
public class PackagesPackage
{
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "version")]
public string Version { get; set; }
}
[XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd")]
[XmlRoot(Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", IsNullable = false, ElementName = "package")]
public class NuSpec
{
[XmlElement("metadata")]
public Metadata MetaData { get; set; }
}
[XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", TypeName = "metadata")]
public class Metadata
{
[XmlArrayItem("dependency", IsNullable = false)]
//TODO: breaks when renamed to capital D
public Dependency[] dependencies { get; set; }
}
[XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", TypeName = "dependencies")]
public class Dependency
{
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "version")]
public string Version { get; set; }
}
}
@@ -1,7 +1,10 @@
using System;
using System.Diagnostics;
using Moq;
using NUnit.Framework;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Serialization;
namespace Umbraco.Tests.Models
@@ -9,6 +12,132 @@ namespace Umbraco.Tests.Models
[TestFixture]
public class UmbracoEntityTests
{
[Test]
public void Validate_Path()
{
var entity = new UmbracoEntity();
//it's empty with no id so we need to allow it
Assert.IsTrue(entity.ValidatePath());
entity.Id = 1234;
//it has an id but no path, so we can't allow it
Assert.IsFalse(entity.ValidatePath());
entity.Path = "-1";
//invalid path
Assert.IsFalse(entity.ValidatePath());
entity.Path = string.Concat("-1,", entity.Id);
//valid path
Assert.IsTrue(entity.ValidatePath());
}
[Test]
public void Ensure_Path_Throws_Without_Id()
{
var entity = new UmbracoEntity();
//no id assigned
Assert.Throws<InvalidOperationException>(() => entity.EnsureValidPath(Mock.Of<ILogger>(), umbracoEntity => new UmbracoEntity(), umbracoEntity => { }));
}
[Test]
public void Ensure_Path_Throws_Without_Parent()
{
var entity = new UmbracoEntity {Id = 1234};
//no parent found
Assert.Throws<NullReferenceException>(() => entity.EnsureValidPath(Mock.Of<ILogger>(), umbracoEntity => null, umbracoEntity => { }));
}
[Test]
public void Ensure_Path_Entity_At_Root()
{
var entity = new UmbracoEntity
{
Id = 1234,
ParentId = -1
};
entity.EnsureValidPath(Mock.Of<ILogger>(), umbracoEntity => null, umbracoEntity => { });
//works because it's under the root
Assert.AreEqual("-1,1234", entity.Path);
}
[Test]
public void Ensure_Path_Entity_Valid_Parent()
{
var entity = new UmbracoEntity
{
Id = 1234,
ParentId = 888
};
entity.EnsureValidPath(Mock.Of<ILogger>(), umbracoEntity => umbracoEntity.ParentId == 888 ? new UmbracoEntity{Id = 888, Path = "-1,888"} : null, umbracoEntity => { });
//works because the parent was found
Assert.AreEqual("-1,888,1234", entity.Path);
}
[Test]
public void Ensure_Path_Entity_Valid_Recursive_Parent()
{
var parentA = new UmbracoEntity
{
Id = 999,
ParentId = -1
};
var parentB = new UmbracoEntity
{
Id = 888,
ParentId = 999
};
var parentC = new UmbracoEntity
{
Id = 777,
ParentId = 888
};
var entity = new UmbracoEntity
{
Id = 1234,
ParentId = 777
};
Func<IUmbracoEntity, IUmbracoEntity> getParent = umbracoEntity =>
{
switch (umbracoEntity.ParentId)
{
case 999:
return parentA;
case 888:
return parentB;
case 777:
return parentC;
case 1234:
return entity;
default:
return null;
}
};
//this will recursively fix all paths
entity.EnsureValidPath(Mock.Of<ILogger>(), getParent, umbracoEntity => { });
Assert.AreEqual("-1,999", parentA.Path);
Assert.AreEqual("-1,999,888", parentB.Path);
Assert.AreEqual("-1,999,888,777", parentC.Path);
Assert.AreEqual("-1,999,888,777,1234", entity.Path);
}
[Test]
public void UmbracoEntity_Can_Be_Initialized_From_Dynamic()
{
@@ -67,6 +67,66 @@ namespace Umbraco.Tests.Persistence.Repositories
return repository;
}
/// <summary>
/// This tests the regression issue of U4-9438
/// </summary>
/// <remarks>
/// The problem was the iteration of the property data in VersionableRepositoryBase when a content item
/// in the list actually doesn't have any property types, it would still skip over a property row.
/// To test, we have 3 content items, the first has properties, the second doesn't and the third does.
/// </remarks>
[Test]
public void Property_Data_Assigned_Correctly()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
ContentTypeRepository contentTypeRepository;
var allContent = new List<IContent>();
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
var emptyContentType = MockedContentTypes.CreateBasicContentType();
var hasPropertiesContentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage");
var content1 = MockedContent.CreateSimpleContent(hasPropertiesContentType);
var content2 = MockedContent.CreateBasicContent(emptyContentType);
var content3 = MockedContent.CreateSimpleContent(hasPropertiesContentType);
// Act
contentTypeRepository.AddOrUpdate(emptyContentType);
contentTypeRepository.AddOrUpdate(hasPropertiesContentType);
repository.AddOrUpdate(content1);
repository.AddOrUpdate(content2);
repository.AddOrUpdate(content3);
unitOfWork.Commit();
allContent.Add(content1);
allContent.Add(content2);
allContent.Add(content3);
}
// Assert
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
//this will cause the GetPropertyCollection to execute and we need to ensure that
// all of the properties and property types are all correct
var result = repository.GetAll(allContent.Select(x => x.Id).ToArray()).ToArray();
foreach (var content in result)
{
foreach (var contentProperty in content.Properties)
{
//prior to the fix, the 2nd document iteration in the GetPropertyCollection would have caused
//the enumerator to move forward past the first property of the 3rd document which would have
//ended up not assiging a property to the 3rd document. This would have ended up with the 3rd
//document still having 3 properties but the last one would not have been assigned an identity
//because the property data would not have been assigned.
Assert.IsTrue(contentProperty.HasIdentity);
}
}
}
}
[Test]
public void Rebuild_Xml_Structures_With_Non_Latest_Version()
{
@@ -172,6 +172,38 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
[Test]
public void Can_Get_All_Containers()
{
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
EntityContainer container1, container2, container3;
using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid))
{
container1 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "container1" };
containerRepository.AddOrUpdate(container1);
container2 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "container2" };
containerRepository.AddOrUpdate(container2);
container3 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "container3" };
containerRepository.AddOrUpdate(container3);
unitOfWork.Commit();
Assert.That(container1.Id, Is.GreaterThan(0));
Assert.That(container2.Id, Is.GreaterThan(0));
Assert.That(container3.Id, Is.GreaterThan(0));
}
using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid))
{
var found1 = containerRepository.Get(container1.Id);
Assert.IsNotNull(found1);
var found2 = containerRepository.Get(container2.Id);
Assert.IsNotNull(found2);
var found3 = containerRepository.Get(container3.Id);
Assert.IsNotNull(found3);
var allContainers = containerRepository.GetAll();
Assert.AreEqual(3, allContainers.Count());
}
}
[Test]
public void Can_Delete_Container()
{
@@ -707,6 +707,35 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
[Test]
public void Path_Is_Set_Correctly_On_Update_With_Master_Template_Removal()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
using (var repository = CreateRepository(unitOfWork))
{
var parent = new Template("parent", "parent");
var child1 = new Template("child1", "child1");
child1.MasterTemplateAlias = parent.Alias;
child1.MasterTemplateId = new Lazy<int>(() => parent.Id);
repository.AddOrUpdate(parent);
repository.AddOrUpdate(child1);
unitOfWork.Commit();
//Act
child1.SetMasterTemplate(null);
repository.AddOrUpdate(child1);
unitOfWork.Commit();
//Assert
Assert.AreEqual(string.Format("-1,{0}", child1.Id), child1.Path);
}
}
[TearDown]
public override void TearDown()
@@ -1,6 +1,8 @@
using System;
using System.Linq;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Store;
using Moq;
using NUnit.Framework;
@@ -31,12 +33,15 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Ensure_Children_Are_Sorted()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var result = searcher.Search(searcher.CreateSearchCriteria().Id(1111).Compile());
Assert.IsNotNull(result);
Assert.AreEqual(1, result.TotalItemCount);
@@ -60,12 +65,15 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Ensure_Result_Has_All_Values()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var result = searcher.Search(searcher.CreateSearchCriteria().Id(1111).Compile());
Assert.IsNotNull(result);
Assert.AreEqual(1, result.TotalItemCount);
@@ -27,6 +27,7 @@ using UmbracoExamine;
using UmbracoExamine.DataServices;
using umbraco.BusinessLogic;
using System.Linq;
using Lucene.Net.Index;
namespace Umbraco.Tests.PublishedContent
{
@@ -106,11 +107,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Ensure_Children_Sorted_With_Examine()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -135,11 +139,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Do_Not_Find_In_Recycle_Bin()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -175,11 +182,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Children_With_Examine()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -197,11 +207,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Descendants_With_Examine()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -219,11 +232,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void DescendantsOrSelf_With_Examine()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -241,12 +257,15 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Ancestors_With_Examine()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var ctx = GetUmbracoContext("/test", 1234);
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
//we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace
@@ -260,12 +279,15 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void AncestorsOrSelf_With_Examine()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var ctx = GetUmbracoContext("/test", 1234);
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
//we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace
@@ -32,7 +32,7 @@ namespace Umbraco.Tests.Routing
}
/// <summary>
/// This checks that when we retrieve a NiceUrl for multiple items that there are no issues with cache overlap
/// This checks that when we retrieve a NiceUrl for multiple items that there are no issues with cache overlap
/// and that they are all cached correctly.
/// </summary>
[Test]
@@ -71,25 +71,19 @@ namespace Umbraco.Tests.Routing
var cache = routingContext.UmbracoContext.ContentCache.InnerCache as PublishedContentCache;
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
var cachedRoutes = cache.RoutesCache.GetCachedRoutes();
Assert.AreEqual(8, cachedRoutes.Count);
Assert.AreEqual(8, cachedRoutes.Count);
foreach (var sample in samples)
{
Assert.IsTrue(cachedRoutes.ContainsKey(sample.Key));
Assert.AreEqual(sample.Value, cachedRoutes[sample.Key]);
}
foreach (var sample in samples)
{
Assert.IsTrue(cachedRoutes.ContainsKey(sample.Key));
Assert.AreEqual(sample.Value, cachedRoutes[sample.Key]);
}
var cachedIds = cache.RoutesCache.GetCachedIds();
Assert.AreEqual(8, cachedIds.Count);
foreach (var sample in samples)
{
var key = sample.Value;
Assert.IsTrue(cachedIds.ContainsKey(key));
Assert.AreEqual(sample.Key, cachedIds[key]);
}
}
var cachedIds = cache.RoutesCache.GetCachedIds();
Assert.AreEqual(0, cachedIds.Count);
}
// test hideTopLevelNodeFromPath false
[TestCase(1046, "/home/")]
@@ -147,10 +141,10 @@ namespace Umbraco.Tests.Routing
var routingContext = GetRoutingContext("http://example.com/test", 1111, umbracoSettings: _umbracoSettings);
Assert.AreEqual("/home/sub1/custom-sub-1/", routingContext.UrlProvider.GetUrl(1177));
requestMock.Setup(x => x.UseDomainPrefixes).Returns(true);
Assert.AreEqual("http://example.com/home/sub1/custom-sub-1/", routingContext.UrlProvider.GetUrl(1177));
@@ -173,14 +167,14 @@ namespace Umbraco.Tests.Routing
Assert.AreEqual("#", routingContext.UrlProvider.GetUrl(999999));
requestMock.Setup(x => x.UseDomainPrefixes).Returns(true);
requestMock.Setup(x => x.UseDomainPrefixes).Returns(true);
Assert.AreEqual("#", routingContext.UrlProvider.GetUrl(999999));
requestMock.Setup(x => x.UseDomainPrefixes).Returns(false);
routingContext.UrlProvider.Mode = UrlProviderMode.Absolute;
Assert.AreEqual("#", routingContext.UrlProvider.GetUrl(999999));
}
}
@@ -0,0 +1,325 @@
using System;
using NUnit.Framework;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web.PublishedCache.XmlPublishedCache;
using Umbraco.Web.Routing;
namespace Umbraco.Tests.Routing
{
// purpose: test the values returned by PublishedContentCache.GetRouteById
// and .GetByRoute (no caching at all, just routing nice urls) including all
// the quirks due to hideTopLevelFromPath and backward compatibility.
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerFixture)]
[TestFixture]
public class NiceUrlRoutesTests : BaseRoutingTest
{
#region Test Setup
protected override void FreezeResolution()
{
SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(new SiteDomainHelper());
base.FreezeResolution();
}
private IUmbracoSettingsSection _umbracoSettings;
public override void Initialize()
{
base.Initialize();
//generate new mock settings and assign so we can configure in individual tests
_umbracoSettings = SettingsForTests.GenerateMockSettings();
SettingsForTests.ConfigureSettings(_umbracoSettings);
}
protected override string GetXmlContent(int templateId)
{
return @"<?xml version=""1.0"" encoding=""utf-8""?>
<!DOCTYPE root[
<!ELEMENT Doc ANY>
<!ATTLIST Doc id ID #REQUIRED>
]>
<root id=""-1"">
<Doc id=""1000"" parentID=""-1"" level=""1"" path=""-1,1000"" nodeName=""A"" urlName=""a"" sortOrder=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" writerName=""admin"" creatorName=""admin"" isDoc="""">
<Doc id=""1001"" parentID=""1000"" level=""2"" path=""-1,1000,1001"" nodeName=""B"" urlName=""b"" sortOrder=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" createDate=""2012-07-20T18:06:45"" updateDate=""2012-07-20T19:07:31"" writerName=""admin"" creatorName=""admin"" isDoc="""">
<Doc id=""1002"" parentID=""1001"" level=""3"" path=""-1,1000,1001,1002"" nodeName=""C"" urlName=""c"" sortOrder=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" createDate=""2012-07-20T18:06:45"" updateDate=""2012-07-20T19:07:31"" writerName=""admin"" creatorName=""admin"" isDoc="""">
<Doc id=""1003"" parentID=""1002"" level=""4"" path=""-1,1000,1001,1002,1003"" nodeName=""D"" urlName=""d"" sortOrder=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" createDate=""2012-07-20T18:06:45"" updateDate=""2012-07-20T19:07:31"" writerName=""admin"" creatorName=""admin"" isDoc="""">
</Doc>
</Doc>
</Doc>
</Doc>
<Doc id=""2000"" parentID=""-1"" level=""1"" path=""-1,2000"" nodeName=""X"" urlName=""x"" sortOrder=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" writerName=""admin"" creatorName=""admin"" isDoc="""">
<Doc id=""2001"" parentID=""2000"" level=""2"" path=""-1,2000,2001"" nodeName=""Y"" urlName=""y"" sortOrder=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" writerName=""admin"" creatorName=""admin"" isDoc="""">
<Doc id=""2002"" parentID=""2001"" level=""3"" path=""-1,2000,2001,2002"" nodeName=""Z"" urlName=""z"" sortOrder=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" writerName=""admin"" creatorName=""admin"" isDoc="""">
</Doc>
</Doc>
<Doc id=""2003"" parentID=""2000"" level=""2"" path=""-1,2000,2003"" nodeName=""A"" urlName=""a"" sortOrder=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" writerName=""admin"" creatorName=""admin"" isDoc="""">
</Doc>
<Doc id=""2004"" parentID=""2000"" level=""2"" path=""-1,2000,2004"" nodeName=""B"" urlName=""b"" sortOrder=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" writerName=""admin"" creatorName=""admin"" isDoc="""">
<Doc id=""2005"" parentID=""2004"" level=""3"" path=""-1,2000,2004,2005"" nodeName=""C"" urlName=""c"" sortOrder=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" writerName=""admin"" creatorName=""admin"" isDoc="""">
</Doc>
<Doc id=""2006"" parentID=""2004"" level=""3"" path=""-1,2000,2004,2006"" nodeName=""E"" urlName=""e"" sortOrder=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" writerName=""admin"" creatorName=""admin"" isDoc="""">
</Doc>
</Doc>
</Doc>
</root>";
}
#endregion
/*
* Just so it's documented somewhere, as of jan. 2017, routes obey the following pseudo-code:
GetByRoute(route, hide = null):
route is "[id]/[path]"
hide = hide ?? global.hide
root = id ? node(id) : document
content = cached(route) ?? DetermineIdByRoute(route, hide)
# route is "1234/path/to/content", finds "content"
# but if there is domain 5678 on "to", the *true* route of "content" is "5678/content"
# so although the route does match, we don't cache it
# there are not other reason not to cache it
if content and no domain between root and content:
cache route (as trusted)
return content
DetermineIdByRoute(route, hide):
route is "[id]/[path]"
try return NavigateRoute(id ?? 0, path, hide:hide)
return null
NavigateRoute(id, path, hide):
if path:
if id:
start = node(id)
else:
start = document
# 'navigate ... from ...' uses lowest sortOrder in case of collision
if hide and ![id]:
# if hiding, then for "/foo" we want to look for "/[any]/foo"
for each child of start:
try return navigate path from child
# but if it fails, we also want to try "/foo"
# fail now if more than one part eg "/foo/bar"
if path is "/[any]/...":
fail
try return navigate path from start
else:
if id:
return node(id)
else:
return root node with lowest sortOrder
GetRouteById(id):
route = cached(id)
if route:
return route
# never cache the route, it may be colliding
route = DetermineRouteById(id)
if route:
cache route (as not trusted)
return route
DetermineRouteById(id):
node = node(id)
walk up from node to domain or root, assemble parts = url segments
if !domain and global.hide:
if id.parent:
# got /top/[path]content, can remove /top
remove top part
else:
# got /content, should remove only if it is the
# node with lowest sort order
root = root node with lowest sortOrder
if root == node:
remove top part
compose path from parts
route = assemble "[domain.id]/[path]"
return route
*/
/*
* The Xml structure for the following tests is:
*
* root
* A 1000
* B 1001
* C 1002
* D 1003
* X 2000
* Y 2001
* Z 2002
* A 2003
* B 2004
* C 2005
* E 2006
*
*/
[TestCase(1000, false, "/a")]
[TestCase(1001, false, "/a/b")]
[TestCase(1002, false, "/a/b/c")]
[TestCase(1003, false, "/a/b/c/d")]
[TestCase(2000, false, "/x")]
[TestCase(2001, false, "/x/y")]
[TestCase(2002, false, "/x/y/z")]
[TestCase(2003, false, "/x/a")]
[TestCase(2004, false, "/x/b")]
[TestCase(2005, false, "/x/b/c")]
[TestCase(2006, false, "/x/b/e")]
[TestCase(1000, true, "/")]
[TestCase(1001, true, "/b")]
[TestCase(1002, true, "/b/c")]
[TestCase(1003, true, "/b/c/d")]
[TestCase(2000, true, "/x")]
[TestCase(2001, true, "/y")]
[TestCase(2002, true, "/y/z")]
[TestCase(2003, true, "/a")]
[TestCase(2004, true, "/b")] // collision!
[TestCase(2005, true, "/b/c")] // collision!
[TestCase(2006, true, "/b/e")] // risky!
public void GetRouteById(int id, bool hide, string expected)
{
var umbracoContext = GetUmbracoContext("/test", 0);
var cache = umbracoContext.ContentCache.InnerCache as PublishedContentCache;
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
SettingsForTests.HideTopLevelNodeFromPath = hide;
const bool preview = true; // make sure we don't cache
var route = cache.GetRouteById(umbracoContext, preview, id);
Assert.AreEqual(expected, route);
}
[Test]
public void GetRouteByIdCache()
{
var umbracoContext = GetUmbracoContext("/test", 0);
var cache = umbracoContext.ContentCache.InnerCache as PublishedContentCache;
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
SettingsForTests.HideTopLevelNodeFromPath = false;
// make sure we cache
PublishedContentCache.UnitTesting = false;
const bool preview = false;
var route = cache.GetRouteById(umbracoContext, preview, 1000);
Assert.AreEqual("/a", route);
// GetRouteById registers a non-trusted route, which is cached for
// id -> route queries (fast GetUrl) but *not* for route -> id
// queries (safe inbound routing)
var cachedRoutes = cache.RoutesCache.GetCachedRoutes();
Assert.AreEqual(1, cachedRoutes.Count);
Assert.IsTrue(cachedRoutes.ContainsKey(1000));
Assert.AreEqual("/a", cachedRoutes[1000]);
var cachedIds = cache.RoutesCache.GetCachedIds();
Assert.AreEqual(0, cachedIds.Count);
}
[TestCase("/", false, 1000)]
[TestCase("/a", false, 1000)] // yes!
[TestCase("/a/b", false, 1001)]
[TestCase("/a/b/c", false, 1002)]
[TestCase("/a/b/c/d", false, 1003)]
[TestCase("/x", false, 2000)]
[TestCase("/", true, 1000)]
[TestCase("/a", true, 2003)]
[TestCase("/a/b", true, -1)]
[TestCase("/x", true, 2000)] // oops!
[TestCase("/x/y", true, -1)] // yes!
[TestCase("/y", true, 2001)]
[TestCase("/y/z", true, 2002)]
[TestCase("/b", true, 1001)] // (hence the 2004 collision)
[TestCase("/b/c", true, 1002)] // (hence the 2005 collision)
public void GetByRoute(string route, bool hide, int expected)
{
var umbracoContext = GetUmbracoContext("/test", 0);
var cache = umbracoContext.ContentCache.InnerCache as PublishedContentCache;
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
SettingsForTests.HideTopLevelNodeFromPath = hide;
const bool preview = true; // make sure we don't cache
var content = cache.GetByRoute(umbracoContext, preview, route);
if (expected < 0)
{
Assert.IsNull(content);
}
else
{
Assert.IsNotNull(content);
Assert.AreEqual(expected, content.Id);
}
}
[Test]
public void GetByRouteCache()
{
var umbracoContext = GetUmbracoContext("/test", 0);
var cache = umbracoContext.ContentCache.InnerCache as PublishedContentCache;
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
SettingsForTests.HideTopLevelNodeFromPath = false;
// make sure we cache
PublishedContentCache.UnitTesting = false;
const bool preview = false;
var content = cache.GetByRoute(umbracoContext, preview, "/a/b/c");
Assert.IsNotNull(content);
Assert.AreEqual(1002, content.Id);
// GetByRoute registers a trusted route, which is cached both for
// id -> route queries (fast GetUrl) and for route -> id queries
// (fast inbound routing)
var cachedRoutes = cache.RoutesCache.GetCachedRoutes();
Assert.AreEqual(1, cachedRoutes.Count);
Assert.IsTrue(cachedRoutes.ContainsKey(1002));
Assert.AreEqual("/a/b/c", cachedRoutes[1002]);
var cachedIds = cache.RoutesCache.GetCachedIds();
Assert.AreEqual(1, cachedIds.Count);
Assert.IsTrue(cachedIds.ContainsKey("/a/b/c"));
Assert.AreEqual(1002, cachedIds["/a/b/c"]);
}
}
}
@@ -79,7 +79,7 @@ namespace Umbraco.Tests.Routing
protected override string GetXmlContent(int templateId)
{
return @"<?xml version=""1.0"" encoding=""utf-8""?>
<!DOCTYPE root[
<!DOCTYPE root[
<!ELEMENT Doc ANY>
<!ATTLIST Doc id ID #REQUIRED>
]>
@@ -302,11 +302,12 @@ namespace Umbraco.Tests.Routing
var cache = routingContext.UmbracoContext.ContentCache.InnerCache as PublishedContentCache;
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
var cachedRoutes = cache.RoutesCache.GetCachedRoutes();
Assert.AreEqual(7, cachedRoutes.Count);
var cachedIds = cache.RoutesCache.GetCachedIds();
Assert.AreEqual(7, cachedIds.Count);
Assert.AreEqual(0, cachedIds.Count);
CheckRoute(cachedRoutes, cachedIds, 1001, "1001/");
CheckRoute(cachedRoutes, cachedIds, 10011, "10011/");
@@ -328,12 +329,11 @@ namespace Umbraco.Tests.Routing
Assert.AreEqual("http://domain1.com/fr/1001-2-1/", routingContext.UrlProvider.GetUrl(100121, new Uri("http://domain2.com"), false));
}
void CheckRoute(IDictionary<int, string> routes, IDictionary<string, int> ids, int id, string route)
private static void CheckRoute(IDictionary<int, string> routes, IDictionary<string, int> ids, int id, string route)
{
Assert.IsTrue(routes.ContainsKey(id));
Assert.AreEqual(route, routes[id]);
Assert.IsTrue(ids.ContainsKey(route));
Assert.AreEqual(id, ids[route]);
Assert.IsFalse(ids.ContainsKey(route));
}
[Test]
@@ -26,7 +26,7 @@ namespace Umbraco.Tests.Routing
public void DoNotPolluteCache()
{
SettingsForTests.UseDirectoryUrls = true;
SettingsForTests.HideTopLevelNodeFromPath = false; // ignored w/domains
SettingsForTests.HideTopLevelNodeFromPath = false; // ignored w/domains
var settings = SettingsForTests.GenerateMockSettings();
var request = Mock.Get(settings.RequestHandler);
@@ -36,7 +36,7 @@ namespace Umbraco.Tests.Routing
RoutingContext routingContext;
string url = "http://domain1.com/1001-1/1001-1-1";
// get the nice url for 100111
routingContext = GetRoutingContext(url, 9999, umbracoSettings: settings);
Assert.AreEqual("http://domain2.com/1001-1-1/", routingContext.UrlProvider.GetUrl(100111, true));
@@ -89,7 +89,7 @@ namespace Umbraco.Tests.Routing
protected override string GetXmlContent(int templateId)
{
return @"<?xml version=""1.0"" encoding=""utf-8""?>
<!DOCTYPE root[
<!DOCTYPE root[
<!ELEMENT Doc ANY>
<!ATTLIST Doc id ID #REQUIRED>
]>
+200 -80
View File
@@ -35,18 +35,64 @@ namespace Umbraco.Tests.Services
[SetUp]
public override void Initialize()
{
base.Initialize();
base.Initialize();
}
[TearDown]
public override void TearDown()
{
base.TearDown();
}
[TearDown]
public override void TearDown()
{
base.TearDown();
}
//TODO Add test to verify there is only ONE newest document/content in cmsDocument table after updating.
//TODO Add test to delete specific version (with and without deleting prior versions) and versions by date.
/// <summary>
/// Ensures that we don't unpublish all nodes when a node is deleted that has an invalid path of -1
/// Note: it is actually the MoveToRecycleBin happening on the initial deletion of a node through the UI
/// that causes the issue.
/// Regression test: http://issues.umbraco.org/issue/U4-9336
/// </summary>
[Test]
public void Moving_Node_To_Recycle_Bin_With_Invalid_Path()
{
var contentService = ServiceContext.ContentService;
var root = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 1);
Assert.IsTrue(contentService.PublishWithStatus(root).Success);
var content = contentService.CreateContentWithIdentity("Test", -1, "umbTextpage", 0);
Assert.IsTrue(contentService.PublishWithStatus(content).Success);
var hierarchy = CreateContentHierarchy().OrderBy(x => x.Level).ToArray();
contentService.Save(hierarchy, 0);
foreach (var c in hierarchy)
{
Assert.IsTrue(contentService.PublishWithStatus(c).Success);
}
//now make the data corrupted :/
DatabaseContext.Database.Execute("UPDATE umbracoNode SET path = '-1' WHERE id = @id", new {id = content.Id});
//re-get with the corrupt path
content = contentService.GetById(content.Id);
// here we get all descendants by the path of the node being moved to bin, and unpublish all of them.
// since the path is invalid, there's logic in here to fix that if it's possible and re-persist the entity.
var moveResult = ServiceContext.ContentService.WithResult().MoveToRecycleBin(content);
Assert.IsTrue(moveResult.Success);
//re-get with the fixed/moved path
content = contentService.GetById(content.Id);
Assert.AreEqual("-1,-20," + content.Id, content.Path);
//re-get
hierarchy = contentService.GetByIds(hierarchy.Select(x => x.Id).ToArray()).OrderBy(x => x.Level).ToArray();
Assert.That(hierarchy.All(c => c.Trashed == false), Is.True);
Assert.That(hierarchy.All(c => c.Path.StartsWith("-1,-20") == false), Is.True);
}
[Test]
public void Remove_Scheduled_Publishing_Date()
{
@@ -104,7 +150,7 @@ namespace Umbraco.Tests.Services
results.Add(contentService.CreateContentWithIdentity("Test", -1, "umbTextpage", 0));
}
var sortedGet = contentService.GetByIds(new[] {results[10].Id, results[5].Id, results[12].Id}).ToArray();
var sortedGet = contentService.GetByIds(new[] { results[10].Id, results[5].Id, results[12].Id }).ToArray();
// Assert
Assert.AreEqual(sortedGet[0].Id, results[10].Id);
@@ -121,7 +167,7 @@ namespace Umbraco.Tests.Services
// Act
for (int i = 0; i < 20; i++)
{
contentService.CreateContentWithIdentity("Test", -1, "umbTextpage", 0);
contentService.CreateContentWithIdentity("Test", -1, "umbTextpage", 0);
}
// Assert
@@ -230,7 +276,7 @@ namespace Umbraco.Tests.Services
// Assert
//there should be no tags for this entity
var tags = tagService.GetTagsForEntity(content1.Id);
var tags = tagService.GetTagsForEntity(content1.Id);
Assert.AreEqual(0, tags.Count());
//these tags should still be returned since they still have actively published content assigned
@@ -265,7 +311,7 @@ namespace Umbraco.Tests.Services
contentService.MoveToRecycleBin(content2);
// Assert
//there should be no exposed content tags now that nothing is published.
var allTags = tagService.GetAllContentTags();
Assert.AreEqual(0, allTags.Count());
@@ -395,7 +441,7 @@ namespace Umbraco.Tests.Services
new PropertyType("test", DataTypeDatabaseType.Ntext, "tags")
{
DataTypeDefinitionId = 1041
});
});
contentTypeService.Save(contentType);
contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy<int>(() => contentType.Id), 0, contentType.Alias) };
@@ -410,7 +456,7 @@ namespace Umbraco.Tests.Services
var child2 = MockedContent.CreateSimpleContent(contentType, "child 2 content", content.Id);
child2.SetTags("tags", new[] { "hello2", "world2" }, true);
contentService.Save(child2);
// Act
contentService.PublishWithChildrenWithStatus(content, includeUnpublished: true);
@@ -461,34 +507,34 @@ namespace Umbraco.Tests.Services
new { nodeId = content.Id, propTypeId = propertyTypeId }));
}
[Test]
public void Can_Replace_Tag_Data_To_Published_Content()
{
[Test]
public void Can_Replace_Tag_Data_To_Published_Content()
{
//Arrange
var contentService = ServiceContext.ContentService;
var contentTypeService = ServiceContext.ContentTypeService;
var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true);
contentType.PropertyGroups.First().PropertyTypes.Add(
new PropertyType("test", DataTypeDatabaseType.Ntext, "tags")
{
DataTypeDefinitionId = 1041
});
{
DataTypeDefinitionId = 1041
});
contentTypeService.Save(contentType);
var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1);
// Act
content.SetTags("tags", new[] { "hello", "world", "some", "tags" }, true);
contentService.Publish(content);
// Assert
Assert.AreEqual(4, content.Properties["tags"].Value.ToString().Split(',').Distinct().Count());
var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id;
Assert.AreEqual(4, DatabaseContext.Database.ExecuteScalar<int>(
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
new {nodeId = content.Id, propTypeId = propertyTypeId}));
}
var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id;
Assert.AreEqual(4, DatabaseContext.Database.ExecuteScalar<int>(
"SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId",
new { nodeId = content.Id, propTypeId = propertyTypeId }));
}
[Test]
public void Can_Append_Tag_Data_To_Published_Content()
@@ -506,7 +552,7 @@ namespace Umbraco.Tests.Services
var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1);
content.SetTags("tags", new[] { "hello", "world", "some", "tags" }, true);
contentService.PublishWithStatus(content);
// Act
content.SetTags("tags", new[] { "another", "world" }, false);
contentService.PublishWithStatus(content);
@@ -548,9 +594,9 @@ namespace Umbraco.Tests.Services
new { nodeId = content.Id, propTypeId = propertyTypeId }));
}
[Test]
public void Can_Remove_Property_Type()
{
[Test]
public void Can_Remove_Property_Type()
{
// Arrange
var contentService = ServiceContext.ContentService;
@@ -560,9 +606,9 @@ namespace Umbraco.Tests.Services
// Assert
Assert.That(content, Is.Not.Null);
Assert.That(content.HasIdentity, Is.False);
}
}
[Test]
[Test]
public void Can_Create_Content()
{
// Arrange
@@ -575,7 +621,7 @@ namespace Umbraco.Tests.Services
Assert.That(content, Is.Not.Null);
Assert.That(content.HasIdentity, Is.False);
}
[Test]
public void Can_Create_Content_Without_Explicitly_Set_User()
{
@@ -595,12 +641,12 @@ namespace Umbraco.Tests.Services
public void Can_Save_New_Content_With_Explicit_User()
{
var user = new User(ServiceContext.UserService.GetUserTypeByAlias("admin"))
{
Name = "Test",
Email = "test@test.com",
Username = "test",
{
Name = "Test",
Email = "test@test.com",
Username = "test",
RawPasswordValue = "test"
};
};
ServiceContext.UserService.Save(user);
var content = new Content("Test", -1, ServiceContext.ContentTypeService.GetContentType("umbTextpage"));
@@ -622,6 +668,17 @@ namespace Umbraco.Tests.Services
Assert.Throws<Exception>(() => contentService.CreateContent("Test", -1, "umbAliasDoesntExist"));
}
[Test]
public void Cannot_Save_Content_With_Empty_Name()
{
// Arrange
var contentService = ServiceContext.ContentService;
var content = new Content(string.Empty, -1, ServiceContext.ContentTypeService.GetContentType("umbTextpage"));
// Act & Assert
Assert.Throws<ArgumentException>(() => contentService.Save(content));
}
[Test]
public void Can_Get_Content_By_Id()
{
@@ -857,14 +914,14 @@ namespace Umbraco.Tests.Services
var provider = new PetaPocoUnitOfWorkProvider(Logger);
using (var uow = provider.GetUnitOfWork())
{
uow.Database.TruncateTable("cmsContentXml");
uow.Database.TruncateTable("cmsContentXml");
}
//for this test we are also going to save a revision for a content item that is not published, this is to ensure
//that it's published version still makes it into the cmsContentXml table!
contentService.Save(allContent.Last());
// Act
var published = contentService.RePublishAll(0);
@@ -872,7 +929,7 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(published);
using (var uow = provider.GetUnitOfWork())
{
Assert.AreEqual(allContent.Count(), uow.Database.ExecuteScalar<int>("select count(*) from cmsContentXml"));
Assert.AreEqual(allContent.Count(), uow.Database.ExecuteScalar<int>("select count(*) from cmsContentXml"));
}
}
@@ -889,7 +946,7 @@ namespace Umbraco.Tests.Services
var allContent = rootContent.Concat(rootContent.SelectMany(x => x.Descendants())).ToList();
//for testing we need to clear out the contentXml table so we can see if it worked
var provider = new PetaPocoUnitOfWorkProvider(Logger);
using (var uow = provider.GetUnitOfWork())
{
uow.Database.TruncateTable("cmsContentXml");
@@ -899,7 +956,7 @@ namespace Umbraco.Tests.Services
contentService.Save(allContent.Last());
// Act
contentService.RePublishAll(new int[]{allContent.Last().ContentTypeId});
contentService.RePublishAll(new int[] { allContent.Last().ContentTypeId });
// Assert
using (var uow = provider.GetUnitOfWork())
@@ -1085,7 +1142,7 @@ namespace Umbraco.Tests.Services
{
// Arrange
var contentService = ServiceContext.ContentService;
var content = contentService.CreateContent("Home US", - 1, "umbTextpage", 0);
var content = contentService.CreateContent("Home US", -1, "umbTextpage", 0);
content.SetValue("author", "Barack Obama");
// Act
@@ -1116,7 +1173,7 @@ namespace Umbraco.Tests.Services
var savedVersion = content.Version;
// Act
var publishedDescendants = ((ContentService) contentService).GetPublishedDescendants(root).ToList();
var publishedDescendants = ((ContentService)contentService).GetPublishedDescendants(root).ToList();
// Assert
Assert.That(rootPublished, Is.True);
@@ -1146,7 +1203,7 @@ namespace Umbraco.Tests.Services
{
// Arrange
var contentService = ServiceContext.ContentService;
var content = contentService.CreateContent("Home US", - 1, "umbTextpage", 0);
var content = contentService.CreateContent("Home US", -1, "umbTextpage", 0);
content.SetValue("author", "Barack Obama");
// Act
@@ -1166,7 +1223,7 @@ namespace Umbraco.Tests.Services
var contentType = contentTypeService.GetContentType("umbTextpage");
Content subpage = MockedContent.CreateSimpleContent(contentType, "Text Subpage 1", NodeDto.NodeIdSeed + 2);
Content subpage2 = MockedContent.CreateSimpleContent(contentType, "Text Subpage 2", NodeDto.NodeIdSeed + 2);
var list = new List<IContent> {subpage, subpage2};
var list = new List<IContent> { subpage, subpage2 };
// Act
contentService.Save(list, 0);
@@ -1186,9 +1243,9 @@ namespace Umbraco.Tests.Services
contentService.Save(hierarchy, 0);
Assert.That(hierarchy.Any(), Is.True);
Assert.That(hierarchy.Any(x => x.HasIdentity == false), Is.False);
//all parent id's should be ok, they are lazy and if they equal zero an exception will be thrown
Assert.DoesNotThrow(() => hierarchy.Any(x => x.ParentId != 0));
Assert.That(hierarchy.Any(x => x.HasIdentity == false), Is.False);
//all parent id's should be ok, they are lazy and if they equal zero an exception will be thrown
Assert.DoesNotThrow(() => hierarchy.Any(x => x.ParentId != 0));
}
@@ -1282,6 +1339,69 @@ namespace Umbraco.Tests.Services
Assert.That(contents.Any(), Is.False);
}
[Test]
public void Can_Empty_RecycleBin_With_Content_That_Has_All_Related_Data()
{
// Arrange
//need to:
// * add relations
// * add permissions
// * add notifications
// * public access
// * tags
// * domain
// * published & preview data
// * multiple versions
var contentType = MockedContentTypes.CreateAllTypesContentType("test", "test");
ServiceContext.ContentTypeService.Save(contentType, 0);
object obj =
new
{
tags = "Hello,World"
};
var content1 = MockedContent.CreateBasicContent(contentType);
content1.PropertyValues(obj);
content1.ResetDirtyProperties(false);
ServiceContext.ContentService.Save(content1, 0);
Assert.IsTrue(ServiceContext.ContentService.PublishWithStatus(content1, 0).Success);
var content2 = MockedContent.CreateBasicContent(contentType);
content2.PropertyValues(obj);
content2.ResetDirtyProperties(false);
ServiceContext.ContentService.Save(content2, 0);
Assert.IsTrue(ServiceContext.ContentService.PublishWithStatus(content2, 0).Success);
ServiceContext.RelationService.Save(new RelationType(Constants.ObjectTypes.DocumentGuid, Constants.ObjectTypes.DocumentGuid, "test"));
Assert.IsNotNull(ServiceContext.RelationService.Relate(content1, content2, "test"));
ServiceContext.PublicAccessService.Save(new PublicAccessEntry(content1, content2, content2, new List<PublicAccessRule>
{
new PublicAccessRule
{
RuleType = "test",
RuleValue = "test"
}
}));
Assert.IsTrue(ServiceContext.PublicAccessService.AddRule(content1, "test2", "test2").Success);
Assert.IsNotNull(ServiceContext.NotificationService.CreateNotification(ServiceContext.UserService.GetUserById(0), content1, "test"));
ServiceContext.ContentService.AssignContentPermission(content1, 'A', new[] {0});
Assert.IsTrue(ServiceContext.DomainService.Save(new UmbracoDomain("www.test.com", "en-AU")
{
RootContentId = content1.Id
}).Success);
// Act
ServiceContext.ContentService.EmptyRecycleBin();
var contents = ServiceContext.ContentService.GetContentInRecycleBin();
// Assert
Assert.That(contents.Any(), Is.False);
}
[Test]
public void Can_Move_Content()
{
@@ -1375,7 +1495,7 @@ namespace Umbraco.Tests.Services
var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage");
var temp = MockedContent.CreateSimpleContent(contentType, "Simple Text Page", -1);
var prop = temp.Properties.First();
temp.SetTags(prop.Alias, new[] {"hello", "world"}, true);
temp.SetTags(prop.Alias, new[] { "hello", "world" }, true);
var status = contentService.PublishWithStatus(temp);
// Act
@@ -1418,14 +1538,14 @@ namespace Umbraco.Tests.Services
[Test]
public void Can_Save_Lazy_Content()
{
var unitOfWork = PetaPocoUnitOfWorkProvider.CreateUnitOfWork(Mock.Of<ILogger>());
{
var unitOfWork = PetaPocoUnitOfWorkProvider.CreateUnitOfWork(Mock.Of<ILogger>());
var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage");
var root = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 1);
var c = new Lazy<IContent>(() => MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Page", root.Id));
var c2 = new Lazy<IContent>(() => MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Subpage", c.Value.Id));
var list = new List<Lazy<IContent>> {c, c2};
var list = new List<Lazy<IContent>> { c, c2 };
ContentTypeRepository contentTypeRepository;
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
@@ -1443,9 +1563,9 @@ namespace Umbraco.Tests.Services
Assert.That(c2.Value.Id > 0, Is.True);
Assert.That(c.Value.ParentId > 0, Is.True);
Assert.That(c2.Value.ParentId > 0, Is.True);
Assert.That(c2.Value.ParentId > 0, Is.True);
}
}
[Test]
@@ -1468,20 +1588,20 @@ namespace Umbraco.Tests.Services
Assert.That(hasPublishedVersion, Is.True);
}
[Test]
public void Can_Verify_Property_Types_On_Content()
{
[Test]
public void Can_Verify_Property_Types_On_Content()
{
// Arrange
var contentTypeService = ServiceContext.ContentTypeService;
var contentTypeService = ServiceContext.ContentTypeService;
var contentType = MockedContentTypes.CreateAllTypesContentType("allDataTypes", "All DataTypes");
contentTypeService.Save(contentType);
var contentService = ServiceContext.ContentService;
var content = MockedContent.CreateAllTypesContent(contentType, "Random Content", -1);
var contentService = ServiceContext.ContentService;
var content = MockedContent.CreateAllTypesContent(contentType, "Random Content", -1);
contentService.Save(content);
var id = content.Id;
var id = content.Id;
// Act
var sut = contentService.GetById(id);
var sut = contentService.GetById(id);
// Arrange
Assert.That(sut.GetValue<bool>("isTrue"), Is.True);
@@ -1496,7 +1616,7 @@ namespace Umbraco.Tests.Services
Assert.That(sut.GetValue<DateTime>("dateTime").ToString("G"), Is.EqualTo(content.GetValue<DateTime>("dateTime").ToString("G")));
Assert.That(sut.GetValue<string>("colorPicker"), Is.EqualTo("black"));
//that one is gone in 7.4
//Assert.That(sut.GetValue<string>("folderBrowser"), Is.Null);
//Assert.That(sut.GetValue<string>("folderBrowser"), Is.Null);
Assert.That(sut.GetValue<string>("ddlMultiple"), Is.EqualTo("1234,1235"));
Assert.That(sut.GetValue<string>("rbList"), Is.EqualTo("random"));
Assert.That(sut.GetValue<DateTime>("date").ToString("G"), Is.EqualTo(content.GetValue<DateTime>("date").ToString("G")));
@@ -1507,23 +1627,23 @@ namespace Umbraco.Tests.Services
Assert.That(sut.GetValue<int>("memberPicker"), Is.EqualTo(1092));
Assert.That(sut.GetValue<string>("relatedLinks"), Is.EqualTo("<links><link title=\"google\" link=\"http://google.com\" type=\"external\" newwindow=\"0\" /></links>"));
Assert.That(sut.GetValue<string>("tags"), Is.EqualTo("this,is,tags"));
}
}
[Test]
public void Can_Delete_Previous_Versions_Not_Latest()
{
[Test]
public void Can_Delete_Previous_Versions_Not_Latest()
{
// Arrange
var contentService = ServiceContext.ContentService;
var content = contentService.GetById(NodeDto.NodeIdSeed + 4);
var version = content.Version;
var version = content.Version;
// Act
// Act
contentService.DeleteVersion(NodeDto.NodeIdSeed + 4, version, true, 0);
var sut = contentService.GetById(NodeDto.NodeIdSeed + 4);
// Assert
Assert.That(sut.Version, Is.EqualTo(version));
}
}
[Test]
public void Ensure_Content_Xml_Created()
@@ -1542,7 +1662,7 @@ namespace Umbraco.Tests.Services
}
contentService.Publish(content);
using (var uow = provider.GetUnitOfWork())
{
Assert.IsTrue(uow.Database.Exists<ContentXmlDto>(content.Id));
@@ -1559,10 +1679,10 @@ namespace Umbraco.Tests.Services
contentService.Save(content);
var provider = new PetaPocoUnitOfWorkProvider(Logger);
using (var uow = provider.GetUnitOfWork())
{
Assert.IsTrue(uow.Database.SingleOrDefault<PreviewXmlDto>("WHERE nodeId=@nodeId AND versionId = @versionId", new{nodeId = content.Id, versionId = content.Version}) != null);
Assert.IsTrue(uow.Database.SingleOrDefault<PreviewXmlDto>("WHERE nodeId=@nodeId AND versionId = @versionId", new { nodeId = content.Id, versionId = content.Version }) != null);
}
}
@@ -1665,11 +1785,11 @@ namespace Umbraco.Tests.Services
var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage");
var root = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 1);
var list = new List<IContent>();
var list = new List<IContent>();
for (int i = 0; i < 10; i++)
{
var content = MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Page " + i, root);
var content = MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Page " + i, root);
list.Add(content);
list.AddRange(CreateChildrenOf(contentType, content, 4));
@@ -1680,12 +1800,12 @@ namespace Umbraco.Tests.Services
return list;
}
private IEnumerable<IContent> CreateChildrenOf(IContentType contentType, IContent content, int depth)
private IEnumerable<IContent> CreateChildrenOf(IContentType contentType, IContent content, int depth)
{
var list = new List<IContent>();
for (int i = 0; i < depth; i++)
{
var c = MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Subpage " + i, content);
var c = MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Subpage " + i, content);
list.Add(c);
Debug.Print("Created: 'Hierarchy Simple Text Subpage {0}' - Depth: {1}", i, depth);
@@ -304,6 +304,42 @@ namespace Umbraco.Tests.Services
Assert.IsNull(deletedContentType);
}
[Test]
public void Can_Create_Container()
{
// Arrange
var cts = ServiceContext.ContentTypeService;
// Act
var container = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid);
container.Name = "container1";
cts.SaveContentTypeContainer(container);
// Assert
var createdContainer = cts.GetContentTypeContainer(container.Id);
Assert.IsNotNull(createdContainer);
}
[Test]
public void Can_Get_All_Containers()
{
// Arrange
var cts = ServiceContext.ContentTypeService;
// Act
var container1 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid);
container1.Name = "container1";
cts.SaveContentTypeContainer(container1);
var container2 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid);
container2.Name = "container2";
cts.SaveContentTypeContainer(container2);
// Assert
var containers = cts.GetContentTypeContainers(new int[0]);
Assert.AreEqual(2, containers.Count());
}
[Test]
public void Deleting_ContentType_Sends_Correct_Number_Of_DeletedEntities_In_Events()
{
@@ -717,6 +753,16 @@ namespace Umbraco.Tests.Services
Assert.DoesNotThrow(() => service.GetContentType("advancedPage"));
}
[Test]
public void Cannot_Save_ContentType_With_Empty_Name()
{
// Arrange
var contentType = MockedContentTypes.CreateSimpleContentType("contentType", string.Empty);
// Act & Assert
Assert.Throws<ArgumentException>(() => ServiceContext.ContentTypeService.Save(contentType));
}
[Test]
public void Cannot_Rename_PropertyType_Alias_On_Composition_Which_Would_Cause_Conflict_In_Other_Composition()
{
@@ -221,5 +221,18 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("preVal1", preVals.PreValuesAsArray.First().Value);
Assert.AreEqual("preVal2", preVals.PreValuesAsArray.Last().Value);
}
[Test]
public void Cannot_Save_DataType_With_Empty_Name()
{
// Arrange
var dataTypeService = ServiceContext.DataTypeService;
// Act
var dataTypeDefinition = new DataTypeDefinition(-1, "Test.TestEditor") { Name = string.Empty, DatabaseType = DataTypeDatabaseType.Ntext };
// Act & Assert
Assert.Throws<ArgumentException>(() => dataTypeService.Save(dataTypeDefinition));
}
}
}
@@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
@@ -217,6 +218,17 @@ namespace Umbraco.Tests.Services
}
[Test]
public void Cannot_Save_Macro_With_Empty_Name()
{
// Arrange
var macroService = ServiceContext.MacroService;
var macro = new Macro("test", string.Empty, scriptPath: "~/Views/MacroPartials/Test.cshtml", cacheDuration: 1234);
// Act & Assert
Assert.Throws<ArgumentException>(() => macroService.Save(macro));
}
//[Test]
//public void Can_Get_Many_By_Alias()
//{
@@ -81,6 +81,19 @@ namespace Umbraco.Tests.Services
Assert.That(mediaChild.Trashed, Is.False);
}
[Test]
public void Cannot_Save_Media_With_Empty_Name()
{
// Arrange
var mediaService = ServiceContext.MediaService;
var mediaType = MockedContentTypes.CreateVideoMediaType();
ServiceContext.ContentTypeService.Save(mediaType);
var media = mediaService.CreateMedia(string.Empty, -1, "video");
// Act & Assert
Assert.Throws<ArgumentException>(() => mediaService.Save(media));
}
[Test]
public void Ensure_Content_Xml_Created()
{
@@ -185,6 +185,18 @@ namespace Umbraco.Tests.Services
Assert.AreEqual(2, membersInRole.Count());
}
[Test]
public void Cannot_Save_Member_With_Empty_Name()
{
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType();
ServiceContext.MemberTypeService.Save(memberType);
IMember member = MockedMember.CreateSimpleMember(memberType, string.Empty, "test@test.com", "pass", "test");
// Act & Assert
Assert.Throws<ArgumentException>(() => ServiceContext.MemberService.Save(member));
}
[TestCase("MyTestRole1", "test1", StringPropertyMatchType.StartsWith, 1)]
[TestCase("MyTestRole1", "test", StringPropertyMatchType.StartsWith, 3)]
[TestCase("MyTestRole1", "test1", StringPropertyMatchType.Exact, 1)]
@@ -173,6 +173,16 @@ namespace Umbraco.Tests.Services
}
}
[Test]
public void Cannot_Save_MemberType_With_Empty_Name()
{
// Arrange
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType("memberTypeAlias", string.Empty);
// Act & Assert
Assert.Throws<ArgumentException>(() => ServiceContext.MemberTypeService.Save(memberType));
}
//[Test]
//public void Can_Save_MemberType_Structure_And_Create_A_Member_Based_On_It()
//{
@@ -490,6 +490,43 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(result4.AllowedSections.Contains("test"));
}
[Test]
public void Cannot_Create_User_With_Empty_Username()
{
// Arrange
var userService = ServiceContext.UserService;
var userType = userService.GetUserTypeByAlias("admin");
// Act & Assert
Assert.Throws<ArgumentException>(() => userService.CreateUserWithIdentity(string.Empty, "john@umbraco.io", userType));
}
[Test]
public void Cannot_Save_User_With_Empty_Username()
{
// Arrange
var userService = ServiceContext.UserService;
var userType = userService.GetUserTypeByAlias("admin");
var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io", userType);
user.Username = string.Empty;
// Act & Assert
Assert.Throws<ArgumentException>(() => userService.Save(user));
}
[Test]
public void Cannot_Save_User_With_Empty_Name()
{
// Arrange
var userService = ServiceContext.UserService;
var userType = userService.GetUserTypeByAlias("admin");
var user = userService.CreateUserWithIdentity("John Doe", "john@umbraco.io", userType);
user.Name = string.Empty;
// Act & Assert
Assert.Throws<ArgumentException>(() => userService.Save(user));
}
[Test]
public void Get_By_Profile_Username()
{
@@ -6,9 +6,18 @@ namespace Umbraco.Tests.TestHelpers.Entities
{
public class MockedContent
{
public static Content CreateBasicContent(IContentType contentType)
{
var content = new Content("Home", -1, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 };
content.ResetDirtyProperties(false);
return content;
}
public static Content CreateSimpleContent(IContentType contentType)
{
var content = new Content("Home", -1, contentType) { Language = "en-US", Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 };
var content = new Content("Home", -1, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 };
object obj =
new
{
@@ -26,7 +35,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
public static Content CreateSimpleContent(IContentType contentType, string name, int parentId)
{
var content = new Content(name, parentId, contentType) { Language = "en-US", CreatorId = 0, WriterId = 0 };
var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 };
object obj =
new
{
@@ -44,7 +53,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
public static Content CreateSimpleContent(IContentType contentType, string name, IContent parent)
{
var content = new Content(name, parent, contentType) { Language = "en-US", CreatorId = 0, WriterId = 0 };
var content = new Content(name, parent, contentType) { CreatorId = 0, WriterId = 0 };
object obj =
new
{
@@ -62,7 +71,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
public static Content CreateTextpageContent(IContentType contentType, string name, int parentId)
{
var content = new Content(name, parentId, contentType) { Language = "en-US", CreatorId = 0, WriterId = 0};
var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0};
object obj =
new
{
@@ -81,7 +90,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
public static Content CreateSimpleContentWithSpecialDatabaseTypes(IContentType contentType, string name, int parentId, string decimalValue, string intValue, DateTime datetimeValue)
{
var content = new Content(name, parentId, contentType) { Language = "en-US", CreatorId = 0, WriterId = 0 };
var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 };
object obj = new
{
decimalProperty = decimalValue,
@@ -96,7 +105,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
public static Content CreateAllTypesContent(IContentType contentType, string name, int parentId)
{
var content = new Content("Random Content Name", parentId, contentType) { Language = "en-US", Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 };
var content = new Content("Random Content Name", parentId, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 };
content.SetValue("isTrue", true);
content.SetValue("number", 42);
@@ -130,7 +139,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
for (int i = 0; i < amount; i++)
{
var name = "Textpage No-" + i;
var content = new Content(name, parentId, contentType) { Language = "en-US", CreatorId = 0, WriterId = 0 };
var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 };
object obj =
new
{
@@ -7,6 +7,13 @@ namespace Umbraco.Tests.TestHelpers.Entities
{
public class MockedContentTypes
{
/// <summary>
/// Creates a content type without any properties
/// </summary>
/// <param name="alias"></param>
/// <param name="name"></param>
/// <param name="parent"></param>
/// <returns></returns>
public static ContentType CreateBasicContentType(string alias = "basePage", string name = "Base Page",
ContentType parent = null)
{
+11 -28
View File
@@ -53,41 +53,32 @@
<ItemGroup>
<Reference Include="AutoMapper, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
<HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="AutoMapper.Net4, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
<HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Examine, Version=0.1.70.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Examine.0.1.70.0\lib\Examine.dll</HintPath>
<Private>True</Private>
<Reference Include="Examine, Version=0.1.81.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Examine.0.1.81\lib\net45\Examine.dll</HintPath>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="log4net, Version=1.2.11.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\log4net-mediumtrust.2.0.0\lib\log4net.dll</HintPath>
</Reference>
<Reference Include="Lucene.Net, Version=2.9.4.1, Culture=neutral, PublicKeyToken=85089178b9ac3181, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
</Reference>
<Reference Include="MiniProfiler, Version=2.1.0.0, Culture=neutral, PublicKeyToken=b44f9351044011a3, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll</HintPath>
</Reference>
<Reference Include="Moq">
<Reference Include="Moq, Version=4.1.1309.919, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.1.1309.0919\lib\net40\Moq.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
@@ -95,14 +86,12 @@
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=2.6.2.12296, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NUnit.2.6.2\lib\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Semver">
<Reference Include="Semver, Version=1.1.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\semver.1.1.2\lib\net45\Semver.dll</HintPath>
</Reference>
<Reference Include="System" />
@@ -111,17 +100,14 @@
<Reference Include="System.Core" />
<Reference Include="System.Data.Entity.Design" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.dll</HintPath>
</Reference>
<Reference Include="System.Data.SqlServerCe.Entity, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll</HintPath>
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.WebRequest" />
@@ -131,58 +117,55 @@
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System.Web.Helpers, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
</Reference>
<Reference Include="System.Web.Http.SelfHost, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.SelfHost.4.0.30506.0\lib\net40\System.Web.Http.SelfHost.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Http.WebHost, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll</HintPath>
</Reference>
<Reference Include="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Services" />
<Reference Include="System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.WebPages.Deployment, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="WebDriver">
<Reference Include="WebDriver, Version=2.32.0.0, Culture=neutral, PublicKeyToken=1c2bd1631853048f, processorArchitecture=MSIL">
<HintPath>..\packages\Selenium.WebDriver.2.32.0\lib\net40\WebDriver.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Dependencies\NuGet.cs" />
<Compile Include="Migrations\MigrationIssuesTests.cs" />
<Compile Include="Persistence\BulkDataReaderTests.cs" />
<Compile Include="Persistence\Migrations\MigrationStartupHandlerTests.cs" />
<Compile Include="Persistence\PetaPocoCachesTest.cs" />
<Compile Include="Persistence\PetaPocoExpressionsTests.cs" />
<Compile Include="Persistence\Repositories\RedirectUrlRepositoryTests.cs" />
<Compile Include="Routing\NiceUrlRoutesTests.cs" />
<Compile Include="TestHelpers\Entities\MockedPropertyTypes.cs" />
<Compile Include="TryConvertToTests.cs" />
<Compile Include="UmbracoExamine\RandomIdRAMDirectory.cs" />
<Compile Include="UmbracoExamine\UmbracoContentIndexerTests.cs" />
<Compile Include="Web\AngularIntegration\AngularAntiForgeryTests.cs" />
<Compile Include="Web\AngularIntegration\ContentModelSerializationTests.cs" />
<Compile Include="Web\AngularIntegration\JsInitializationTests.cs" />
@@ -612,9 +595,9 @@
<SubType>Designer</SubType>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
<EmbeddedResource Include="TreesAndSections\applications.config" />
<EmbeddedResource Include="TreesAndSections\trees.config" />
<None Include="packages.config" />
<None Include="Packaging\Packages\Document_Type_Picker_1.1.umb" />
<None Include="UmbracoExamine\TestFiles\umbraco-sort.config">
<SubType>Designer</SubType>
+25 -37
View File
@@ -1,6 +1,8 @@
using System;
using System.Linq;
using Examine;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Index;
using Lucene.Net.Store;
using NUnit.Framework;
using Umbraco.Tests.TestHelpers;
@@ -15,53 +17,39 @@ namespace Umbraco.Tests.UmbracoExamine
[Test]
public void Events_Ignoring_Node()
{
//change the parent id so that they are all ignored
var existingCriteria = _indexer.IndexerData;
_indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
999); //change to 999
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
//change the parent id so that they are all ignored
var existingCriteria = indexer.IndexerData;
indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
999); //change to 999
var isIgnored = false;
var isIgnored = false;
EventHandler<IndexingNodeDataEventArgs> ignoringNode = (s, e) =>
{
isIgnored = true;
};
EventHandler<IndexingNodeDataEventArgs> ignoringNode = (s, e) =>
{
isIgnored = true;
};
_indexer.IgnoringNode += ignoringNode;
indexer.IgnoringNode += ignoringNode;
//get a node from the data repo
var node = _contentService.GetPublishedContentByXPath("//*[string-length(@id)>0 and number(@id)>0]")
.Root
.Elements()
.First();
//get a node from the data repo
var node = _contentService.GetPublishedContentByXPath("//*[string-length(@id)>0 and number(@id)>0]")
.Root
.Elements()
.First();
_indexer.ReIndexNode(node, IndexTypes.Content);
indexer.ReIndexNode(node, IndexTypes.Content);
Assert.IsTrue(isIgnored);
Assert.IsTrue(isIgnored);
}
}
private readonly TestContentService _contentService = new TestContentService();
private static UmbracoExamineSearcher _searcher;
private static UmbracoContentIndexer _indexer;
private Lucene.Net.Store.Directory _luceneDir;
public override void Initialize()
{
base.Initialize();
_luceneDir = new RAMDirectory();
_indexer = IndexInitializer.GetUmbracoIndexer(_luceneDir);
_indexer.RebuildIndex();
_searcher = IndexInitializer.GetUmbracoSearcher(_luceneDir);
}
public override void TearDown()
{
base.TearDown();
_luceneDir.Dispose();
}
}
}
@@ -7,6 +7,7 @@ using Examine.LuceneEngine.Config;
using Examine.LuceneEngine.Providers;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Index;
using Lucene.Net.Store;
using Moq;
using Umbraco.Core;
@@ -33,7 +34,7 @@ namespace Umbraco.Tests.UmbracoExamine
internal static class IndexInitializer
{
public static UmbracoContentIndexer GetUmbracoIndexer(
Directory luceneDir,
IndexWriter writer,
Analyzer analyzer = null,
IDataService dataService = null,
IContentService contentService = null,
@@ -41,7 +42,8 @@ namespace Umbraco.Tests.UmbracoExamine
IDataTypeService dataTypeService = null,
IMemberService memberService = null,
IUserService userService = null,
IContentTypeService contentTypeService = null)
IContentTypeService contentTypeService = null,
bool supportUnpublishedContent = false)
{
if (dataService == null)
{
@@ -177,15 +179,17 @@ namespace Umbraco.Tests.UmbracoExamine
var indexCriteria = indexSet.ToIndexCriteria(dataService, UmbracoContentIndexer.IndexFieldPolicies);
var i = new UmbracoContentIndexer(indexCriteria,
luceneDir, //custom lucene directory
dataService,
contentService,
mediaService,
dataTypeService,
userService,
contentTypeService,
analyzer,
false);
writer,
dataService,
contentService,
mediaService,
dataTypeService,
userService,
contentTypeService,
false)
{
SupportUnpublishedContent = supportUnpublishedContent
};
//i.IndexSecondsInterval = 1;
@@ -193,13 +197,14 @@ namespace Umbraco.Tests.UmbracoExamine
return i;
}
public static UmbracoExamineSearcher GetUmbracoSearcher(Directory luceneDir, Analyzer analyzer = null)
public static UmbracoExamineSearcher GetUmbracoSearcher(IndexWriter writer, Analyzer analyzer = null)
{
if (analyzer == null)
{
analyzer = new StandardAnalyzer(Version.LUCENE_29);
}
return new UmbracoExamineSearcher(luceneDir, analyzer);
return new UmbracoExamineSearcher(writer, analyzer);
}
public static LuceneSearcher GetLuceneSearcher(Directory luceneDir)
+181 -169
View File
@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Examine;
using Examine.LuceneEngine;
using Examine.LuceneEngine.Providers;
using Examine.LuceneEngine.SearchCriteria;
using Examine.SearchCriteria;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
@@ -15,218 +15,230 @@ using UmbracoExamine;
namespace Umbraco.Tests.UmbracoExamine
{
/// <summary>
/// Tests the standard indexing capabilities
/// </summary>
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)]
[TestFixture, RequiresSTA]
public class IndexTest : ExamineBaseTest
{
{
/// <summary>
/// Check that the node signalled as protected in the content service is not present in the index.
/// </summary>
[Test]
public void Index_Protected_Content_Not_Indexed()
{
///// <summary>
/// <summary>
/// Check that the node signalled as protected in the content service is not present in the index.
/// </summary>
[Test]
public void Index_Protected_Content_Not_Indexed()
{
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
indexer.RebuildIndex();
var protectedQuery = new BooleanQuery();
protectedQuery.Add(
new BooleanClause(
new TermQuery(new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content)),
BooleanClause.Occur.MUST));
var protectedQuery = new BooleanQuery();
protectedQuery.Add(
new BooleanClause(
new TermQuery(new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content)),
BooleanClause.Occur.MUST));
protectedQuery.Add(
new BooleanClause(
new TermQuery(new Term(LuceneIndexer.IndexNodeIdFieldName, TestContentService.ProtectedNode.ToString())),
BooleanClause.Occur.MUST));
protectedQuery.Add(
new BooleanClause(
new TermQuery(new Term(LuceneIndexer.IndexNodeIdFieldName, TestContentService.ProtectedNode.ToString())),
BooleanClause.Occur.MUST));
var collector = new AllHitsCollector(false, true);
var s = _searcher.GetSearcher();
s.Search(protectedQuery, collector);
var collector = new AllHitsCollector(false, true);
var s = searcher.GetSearcher();
s.Search(protectedQuery, collector);
Assert.AreEqual(0, collector.Count, "Protected node should not be indexed");
Assert.AreEqual(0, collector.Count, "Protected node should not be indexed");
}
}
}
[Test]
public void Index_Move_Media_From_Non_Indexable_To_Indexable_ParentID()
{
//change parent id to 1116
var existingCriteria = _indexer.IndexerData;
_indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
1116);
//rebuild so it excludes children unless they are under 1116
_indexer.RebuildIndex();
[Test]
public void Index_Move_Media_From_Non_Indexable_To_Indexable_ParentID()
{
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
indexer.RebuildIndex();
//ensure that node 2112 doesn't exist
var results = _searcher.Search(_searcher.CreateSearchCriteria().Id(2112).Compile());
Assert.AreEqual(0, results.Count());
var mediaService = new TestMediaService();
//get a node from the data repo (this one exists underneath 2222)
var node = _mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]")
.Root
.Elements()
.Where(x => (int)x.Attribute("id") == 2112)
.First();
//change parent id to 1116
var existingCriteria = indexer.IndexerData;
indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
1116);
var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112
Assert.AreEqual("-1,1111,2222,2112", currPath);
//rebuild so it excludes children unless they are under 1116
indexer.RebuildIndex();
//now mimic moving 2112 to 1116
//node.SetAttributeValue("path", currPath.Replace("2222", "1116"));
node.SetAttributeValue("path", "-1,1116,2112");
node.SetAttributeValue("parentID", "1116");
//ensure that node 2112 doesn't exist
var results = searcher.Search(searcher.CreateSearchCriteria().Id(2112).Compile());
Assert.AreEqual(0, results.TotalItemCount);
//now reindex the node, this should first delete it and then WILL add it because of the parent id constraint
_indexer.ReIndexNode(node, IndexTypes.Media);
//get a node from the data repo (this one exists underneath 2222)
var node = mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]")
.Root
.Elements()
.First(x => (int)x.Attribute("id") == 2112);
//RESET the parent id
existingCriteria = ((IndexCriteria)_indexer.IndexerData);
_indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
null);
var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112
Assert.AreEqual("-1,1111,2222,2112", currPath);
//now ensure it's deleted
var newResults = _searcher.Search(_searcher.CreateSearchCriteria().Id(2112).Compile());
Assert.AreEqual(1, newResults.Count());
}
//now mimic moving 2112 to 1116
//node.SetAttributeValue("path", currPath.Replace("2222", "1116"));
node.SetAttributeValue("path", "-1,1116,2112");
node.SetAttributeValue("parentID", "1116");
[Test]
[Ignore]
public void Index_Move_Media_To_Non_Indexable_ParentID()
{
//get a node from the data repo (this one exists underneath 2222)
var node = _mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]")
.Root
.Elements()
.Where(x => (int)x.Attribute("id") == 2112)
.First();
//now reindex the node, this should first delete it and then WILL add it because of the parent id constraint
indexer.ReIndexNode(node, IndexTypes.Media);
var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112
Assert.AreEqual("-1,1111,2222,2112", currPath);
//RESET the parent id
existingCriteria = ((IndexCriteria)indexer.IndexerData);
indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
null);
//ensure it's indexed
_indexer.ReIndexNode(node, IndexTypes.Media);
//now ensure it's deleted
var newResults = searcher.Search(searcher.CreateSearchCriteria().Id(2112).Compile());
Assert.AreEqual(1, newResults.TotalItemCount);
}
//change the parent node id to be the one it used to exist under
var existingCriteria = _indexer.IndexerData;
_indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
2222);
}
//now mimic moving the node underneath 1116 instead of 2222
node.SetAttributeValue("path", currPath.Replace("2222", "1116"));
node.SetAttributeValue("parentID", "1116");
[Test]
public void Index_Move_Media_To_Non_Indexable_ParentID()
{
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
indexer.RebuildIndex();
//now reindex the node, this should first delete it and then NOT add it because of the parent id constraint
_indexer.ReIndexNode(node, IndexTypes.Media);
var mediaService = new TestMediaService();
//RESET the parent id
existingCriteria = ((IndexCriteria)_indexer.IndexerData);
_indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
null);
//get a node from the data repo (this one exists underneath 2222)
var node = mediaService.GetLatestMediaByXpath("//*[string-length(@id)>0 and number(@id)>0]")
.Root
.Elements()
.First(x => (int)x.Attribute("id") == 2112);
//now ensure it's deleted
var results = _searcher.Search(_searcher.CreateSearchCriteria().Id(2112).Compile());
Assert.AreEqual(0, results.Count());
var currPath = (string)node.Attribute("path"); //should be : -1,1111,2222,2112
Assert.AreEqual("-1,1111,2222,2112", currPath);
}
//ensure it's indexed
indexer.ReIndexNode(node, IndexTypes.Media);
//change the parent node id to be the one it used to exist under
var existingCriteria = indexer.IndexerData;
indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
2222);
//now mimic moving the node underneath 1116 instead of 2222
node.SetAttributeValue("path", currPath.Replace("2222", "1116"));
node.SetAttributeValue("parentID", "1116");
//now reindex the node, this should first delete it and then NOT add it because of the parent id constraint
indexer.ReIndexNode(node, IndexTypes.Media);
//RESET the parent id
existingCriteria = ((IndexCriteria)indexer.IndexerData);
indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes,
null);
//now ensure it's deleted
var results = searcher.Search(searcher.CreateSearchCriteria().Id(2112).Compile());
Assert.AreEqual(0, results.TotalItemCount);
}
}
/// <summary>
/// This will ensure that all 'Content' (not media) is cleared from the index using the Lucene API directly.
/// We then call the Examine method to re-index Content and do some comparisons to ensure that it worked correctly.
/// </summary>
[Test]
public void Index_Reindex_Content()
{
var s = (IndexSearcher)_searcher.GetSearcher();
/// <summary>
/// This will ensure that all 'Content' (not media) is cleared from the index using the Lucene API directly.
/// We then call the Examine method to re-index Content and do some comparisons to ensure that it worked correctly.
/// </summary>
[Test]
public void Index_Reindex_Content()
{
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer, supportUnpublishedContent: true))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
indexer.RebuildIndex();
//first delete all 'Content' (not media). This is done by directly manipulating the index with the Lucene API, not examine!
var contentTerm = new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content);
var writer = _indexer.GetIndexWriter();
writer.DeleteDocuments(contentTerm);
writer.Commit();
var s = (IndexSearcher)searcher.GetSearcher();
//make sure the content is gone. This is done with lucene APIs, not examine!
var collector = new AllHitsCollector(false, true);
var query = new TermQuery(contentTerm);
s = (IndexSearcher)_searcher.GetSearcher(); //make sure the searcher is up do date.
s.Search(query, collector);
Assert.AreEqual(0, collector.Count);
//first delete all 'Content' (not media). This is done by directly manipulating the index with the Lucene API, not examine!
//call our indexing methods
_indexer.IndexAll(IndexTypes.Content);
var contentTerm = new Term(LuceneIndexer.IndexTypeFieldName, IndexTypes.Content);
writer.DeleteDocuments(contentTerm);
writer.Commit();
collector = new AllHitsCollector(false, true);
s = (IndexSearcher)_searcher.GetSearcher(); //make sure the searcher is up do date.
s.Search(query, collector);
//var ids = new List<string>();
//for (var i = 0; i < collector.Count;i++)
//{
// ids.Add(s.Doc(collector.GetDocId(i)).GetValues("__NodeId")[0]);
//}
Assert.AreEqual(20, collector.Count);
}
//make sure the content is gone. This is done with lucene APIs, not examine!
var collector = new AllHitsCollector(false, true);
var query = new TermQuery(contentTerm);
s = (IndexSearcher)searcher.GetSearcher(); //make sure the searcher is up do date.
s.Search(query, collector);
Assert.AreEqual(0, collector.Count);
/// <summary>
/// This will delete an item from the index and ensure that all children of the node are deleted too!
/// </summary>
[Test]
[Ignore]
public void Index_Delete_Index_Item_Ensure_Heirarchy_Removed()
{
//call our indexing methods
indexer.IndexAll(IndexTypes.Content);
//now delete a node that has children
collector = new AllHitsCollector(false, true);
s = (IndexSearcher)searcher.GetSearcher(); //make sure the searcher is up do date.
s.Search(query, collector);
//var ids = new List<string>();
//for (var i = 0; i < collector.Count;i++)
//{
// ids.Add(s.Doc(collector.GetDocId(i)).GetValues("__NodeId")[0]);
//}
Assert.AreEqual(21, collector.Count);
}
}
_indexer.DeleteFromIndex(1140.ToString());
//this node had children: 1141 & 1142, let's ensure they are also removed
/// <summary>
/// This will delete an item from the index and ensure that all children of the node are deleted too!
/// </summary>
[Test]
public void Index_Delete_Index_Item_Ensure_Heirarchy_Removed()
{
var results = _searcher.Search(_searcher.CreateSearchCriteria().Id(1141).Compile());
Assert.AreEqual(0, results.Count());
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
indexer.RebuildIndex();
results = _searcher.Search(_searcher.CreateSearchCriteria().Id(1142).Compile());
Assert.AreEqual(0, results.Count());
//now delete a node that has children
}
indexer.DeleteFromIndex(1140.ToString());
//this node had children: 1141 & 1142, let's ensure they are also removed
#region Private methods and properties
var results = searcher.Search(searcher.CreateSearchCriteria().Id(1141).Compile());
Assert.AreEqual(0, results.TotalItemCount);
private readonly TestContentService _contentService = new TestContentService();
private readonly TestMediaService _mediaService = new TestMediaService();
private static UmbracoExamineSearcher _searcher;
private static UmbracoContentIndexer _indexer;
#endregion
#region Initialize and Cleanup
private Lucene.Net.Store.Directory _luceneDir;
public override void TearDown()
{
base.TearDown();
_luceneDir.Dispose();
results = searcher.Search(searcher.CreateSearchCriteria().Id(1142).Compile());
Assert.AreEqual(0, results.TotalItemCount);
}
}
#region Initialize and Cleanup
public override void TearDown()
{
base.TearDown();
UmbracoExamineSearcher.DisableInitializationCheck = null;
BaseUmbracoIndexer.DisableInitializationCheck = null;
}
public override void Initialize()
{
base.Initialize();
_luceneDir = new RAMDirectory();
_indexer = IndexInitializer.GetUmbracoIndexer(_luceneDir);
_indexer.RebuildIndex();
_searcher = IndexInitializer.GetUmbracoSearcher(_luceneDir);
}
#endregion
}
#endregion
}
}
@@ -0,0 +1,14 @@
using System;
using Lucene.Net.Store;
namespace Umbraco.Tests.UmbracoExamine
{
public class RandomIdRAMDirectory : RAMDirectory
{
private readonly string _lockId = Guid.NewGuid().ToString();
public override string GetLockID()
{
return _lockId;
}
}
}
+13 -31
View File
@@ -8,6 +8,8 @@ using Examine.LuceneEngine.Providers;
using Lucene.Net.Store;
using NUnit.Framework;
using Examine.LuceneEngine.SearchCriteria;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Index;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.UmbracoExamine
@@ -20,19 +22,17 @@ namespace Umbraco.Tests.UmbracoExamine
[Test]
public void Test_Sort_Order_Sorting()
{
//var newIndexFolder = new DirectoryInfo(Path.Combine("App_Data\\SearchTests", Guid.NewGuid().ToString()));
//System.IO.Directory.CreateDirectory(newIndexFolder.FullName);
using (var luceneDir = new RAMDirectory())
//using (var luceneDir = new SimpleFSDirectory(newIndexFolder))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir, null,
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer, null,
new TestDataService()
{
ContentService = new TestContentService(TestFiles.umbraco_sort)
});
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
{
ContentService = new TestContentService(TestFiles.umbraco_sort)
},
supportUnpublishedContent: true))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
indexer.RebuildIndex();
var s = (LuceneSearcher)searcher;
var luceneSearcher = s.GetSearcher();
@@ -69,25 +69,7 @@ namespace Umbraco.Tests.UmbracoExamine
currentSort = sort;
}
return true;
}
//[Test]
//public void Test_Index_Type_With_German_Analyzer()
//{
// using (var luceneDir = new RAMDirectory())
// {
// var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir,
// new GermanAnalyzer());
// indexer.RebuildIndex();
// var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
// }
//}
//private readonly TestContentService _contentService = new TestContentService();
//private readonly TestMediaService _mediaService = new TestMediaService();
//private static UmbracoExamineSearcher _searcher;
//private static UmbracoContentIndexer _indexer;
//private Lucene.Net.Store.Directory _luceneDir;
}
}
}
@@ -0,0 +1,107 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using UmbracoExamine;
namespace Umbraco.Tests.UmbracoExamine
{
[TestFixture]
public class UmbracoContentIndexerTests : ExamineBaseTest
{
[Test]
public void Get_Serialized_Content_No_Published_Content()
{
var contentSet = new List<IContent>
{
Mock.Of<IContent>(c => c.Id == 1 && c.Path == "-1,1" && c.Published && c.Level == 1),
Mock.Of<IContent>(c => c.Id == 2 && c.Path == "-1,2" && c.Published && c.Level == 1),
Mock.Of<IContent>(c => c.Id == 3 && c.Path == "-1,3" && c.Published == false && c.Level == 1), // no
Mock.Of<IContent>(c => c.Id == 4 && c.Path == "-1,4" && c.Published == false && c.Level == 1), // no
Mock.Of<IContent>(c => c.Id == 5 && c.Path == "-1,1,5" && c.Published && c.Level == 2),
Mock.Of<IContent>(c => c.Id == 6 && c.Path == "-1,2,6" && c.Published == false && c.Level == 2), // no
Mock.Of<IContent>(c => c.Id == 7 && c.Path == "-1,3,7" && c.Published && c.Level == 2), // no
Mock.Of<IContent>(c => c.Id == 8 && c.Path == "-1,4,8" && c.Published && c.Level == 2), // no
Mock.Of<IContent>(c => c.Id == 9 && c.Path == "-1,4,9" && c.Published && c.Level == 2), // no
Mock.Of<IContent>(c => c.Id == 10 && c.Path == "-1,1,5,10" && c.Published && c.Level == 3),
Mock.Of<IContent>(c => c.Id == 15 && c.Path == "-1,1,5,15" && c.Published && c.Level == 3),
Mock.Of<IContent>(c => c.Id == 11 && c.Path == "-1,2,6,11" && c.Published && c.Level == 3), // no
Mock.Of<IContent>(c => c.Id == 16 && c.Path == "-1,2,6,16" && c.Published && c.Level == 3), // no
Mock.Of<IContent>(c => c.Id == 12 && c.Path == "-1,3,7,12" && c.Published && c.Level == 3), // no
Mock.Of<IContent>(c => c.Id == 17 && c.Path == "-1,3,7,17" && c.Published && c.Level == 3), // no
Mock.Of<IContent>(c => c.Id == 13 && c.Path == "-1,4,8,13" && c.Published && c.Level == 3), // no
Mock.Of<IContent>(c => c.Id == 18 && c.Path == "-1,4,8,18" && c.Published && c.Level == 3), // no
Mock.Of<IContent>(c => c.Id == 14 && c.Path == "-1,4,9,14" && c.Published && c.Level == 3), // no
Mock.Of<IContent>(c => c.Id == 19 && c.Path == "-1,4,9,19" && c.Published && c.Level == 3), // no
};
//ensure the rest of the required values are populted
foreach (var content in contentSet)
{
var mock = Mock.Get(content);
mock.Setup(x => x.ContentType).Returns(Mock.Of<IContentType>(type => type.Icon == "hello"));
}
contentSet.Sort((a, b) => Comparer<int>.Default.Compare(a.Level, b.Level));
var published = new HashSet<string>();
var result = UmbracoContentIndexer.GetSerializedContent(false, content => new XElement("test"), contentSet, published)
.WhereNotNull()
.ToArray();
Assert.AreEqual(5, result.Length);
}
[Test]
public void Get_Serialized_Content_With_Published_Content()
{
var contentSet = new List<IContent>
{
Mock.Of<IContent>(c => c.Id == 1 && c.Path == "-1,1" && c.Published && c.Level == 1),
Mock.Of<IContent>(c => c.Id == 2 && c.Path == "-1,2" && c.Published && c.Level == 1),
Mock.Of<IContent>(c => c.Id == 3 && c.Path == "-1,3" && c.Published == false && c.Level == 1),
Mock.Of<IContent>(c => c.Id == 4 && c.Path == "-1,4" && c.Published == false && c.Level == 1),
Mock.Of<IContent>(c => c.Id == 5 && c.Path == "-1,1,5" && c.Published && c.Level == 2),
Mock.Of<IContent>(c => c.Id == 6 && c.Path == "-1,2,6" && c.Published == false && c.Level == 2),
Mock.Of<IContent>(c => c.Id == 7 && c.Path == "-1,3,7" && c.Published && c.Level == 2),
Mock.Of<IContent>(c => c.Id == 8 && c.Path == "-1,4,8" && c.Published && c.Level == 2),
Mock.Of<IContent>(c => c.Id == 9 && c.Path == "-1,4,9" && c.Published && c.Level == 2),
Mock.Of<IContent>(c => c.Id == 10 && c.Path == "-1,1,5,10" && c.Published && c.Level == 3),
Mock.Of<IContent>(c => c.Id == 15 && c.Path == "-1,1,5,15" && c.Published && c.Level == 3),
Mock.Of<IContent>(c => c.Id == 11 && c.Path == "-1,2,6,11" && c.Published && c.Level == 3),
Mock.Of<IContent>(c => c.Id == 16 && c.Path == "-1,2,6,16" && c.Published && c.Level == 3),
Mock.Of<IContent>(c => c.Id == 12 && c.Path == "-1,3,7,12" && c.Published && c.Level == 3),
Mock.Of<IContent>(c => c.Id == 17 && c.Path == "-1,3,7,17" && c.Published && c.Level == 3),
Mock.Of<IContent>(c => c.Id == 13 && c.Path == "-1,4,8,13" && c.Published && c.Level == 3),
Mock.Of<IContent>(c => c.Id == 18 && c.Path == "-1,4,8,18" && c.Published && c.Level == 3),
Mock.Of<IContent>(c => c.Id == 14 && c.Path == "-1,4,9,14" && c.Published && c.Level == 3),
Mock.Of<IContent>(c => c.Id == 19 && c.Path == "-1,4,9,19" && c.Published && c.Level == 3),
};
//ensure the rest of the required values are populted
foreach (var content in contentSet)
{
var mock = Mock.Get(content);
mock.Setup(x => x.ContentType).Returns(Mock.Of<IContentType>(type => type.Icon == "hello"));
}
contentSet.Sort((a, b) => Comparer<int>.Default.Compare(a.Level, b.Level));
var published = new HashSet<string>();
var result = UmbracoContentIndexer.GetSerializedContent(true, content => new XElement("test"), contentSet, published)
.WhereNotNull()
.ToArray();
Assert.AreEqual(19, result.Length);
}
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
<packages>
<package id="AspNetWebApi.SelfHost" version="4.0.20710.0" targetFramework="net45" />
<package id="AutoMapper" version="3.3.1" targetFramework="net45" />
<package id="Examine" version="0.1.70.0" targetFramework="net45" />
<package id="Examine" version="0.1.81" targetFramework="net45" />
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net45" />
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net45" />
<package id="Microsoft.AspNet.Mvc" version="5.2.3" targetFramework="net45" />
@@ -69,22 +69,6 @@
};
}
if (!String.prototype.htmlEncode) {
/** htmlEncode extension method for string */
String.prototype.htmlEncode = function () {
//create a in-memory div, set it's inner text(which jQuery automatically encodes)
//then grab the encoded contents back out. The div never exists on the page.
return $('<div/>').text(this).html();
};
}
if (!String.prototype.htmlDecode) {
/** htmlDecode extension method for string */
String.prototype.htmlDecode = function () {
return $('<div/>').html(this).text();
};
}
if (!String.prototype.startsWith) {
/** startsWith extension method for string */
String.prototype.startsWith = function (str) {
@@ -172,6 +172,55 @@ angular.module("umbraco.directives")
});
// pin toolbar to top of screen if we have focus and it scrolls off the screen
var pinToolbar = function () {
var _toolbar = $(editor.editorContainer).find(".mce-toolbar");
var toolbarHeight = _toolbar.height();
var _tinyMce = $(editor.editorContainer);
var tinyMceRect = _tinyMce[0].getBoundingClientRect();
var tinyMceTop = tinyMceRect.top;
var tinyMceBottom = tinyMceRect.bottom;
var tinyMceWidth = tinyMceRect.width;
var _tinyMceEditArea = _tinyMce.find(".mce-edit-area");
// set padding in top of mce so the content does not "jump" up
_tinyMceEditArea.css("padding-top", toolbarHeight);
if (tinyMceTop < 160 && ((160 + toolbarHeight) < tinyMceBottom)) {
_toolbar
.css("visibility", "visible")
.css("position", "fixed")
.css("top", "160px")
.css("margin-top", "0")
.css("width", tinyMceWidth);
} else {
_toolbar
.css("visibility", "visible")
.css("position", "absolute")
.css("top", "auto")
.css("margin-top", "0")
.css("width", tinyMceWidth);
}
};
// unpin toolbar to top of screen
var unpinToolbar = function() {
var _toolbar = $(editor.editorContainer).find(".mce-toolbar");
var _tinyMce = $(editor.editorContainer);
var _tinyMceEditArea = _tinyMce.find(".mce-edit-area");
// reset padding in top of mce so the content does not "jump" up
_tinyMceEditArea.css("padding-top", "0");
_toolbar.css("position", "static");
};
//when we leave the editor (maybe)
editor.on('blur', function (e) {
editor.save();
@@ -185,6 +234,9 @@ angular.module("umbraco.directives")
scope.onBlur();
}
unpinToolbar();
$('.umb-panel-body').off('scroll', pinToolbar);
});
});
@@ -196,6 +248,9 @@ angular.module("umbraco.directives")
scope.onFocus();
}
pinToolbar();
$('.umb-panel-body').on('scroll', pinToolbar);
});
});
@@ -207,6 +262,9 @@ angular.module("umbraco.directives")
scope.onClick();
}
pinToolbar();
$('.umb-panel-body').on('scroll', pinToolbar);
});
});
@@ -103,15 +103,15 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
* @description
* This returns a promise with an underlying http call, it is a helper method to reduce
* the amount of duplicate code needed to query http resources and automatically handle any
* Http errors. See /docs/source/using-promises-resources.md
* 500 Http server errors.
*
* @param {object} opts A mixed object which can either be a string representing the error message to be
* returned OR an object containing either:
* @param {object} opts A mixed object which can either be a `string` representing the error message to be
* returned OR an `object` containing either:
* { success: successCallback, errorMsg: errorMessage }
* OR
* { success: successCallback, error: errorCallback }
* In both of the above, the successCallback must accept these parameters: data, status, headers, config
* If using the errorCallback it must accept these parameters: data, status, headers, config
* In both of the above, the successCallback must accept these parameters: `data`, `status`, `headers`, `config`
* If using the errorCallback it must accept these parameters: `data`, `status`, `headers`, `config`
* The success callback must return the data which will be resolved by the deferred object.
* The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status }
*/

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