Compare commits

...

246 Commits

Author SHA1 Message Date
Poornima Nayar 157c0a2479 Added Review process file 2019-09-02 10:27:29 +02:00
Poornima Nayar 3d3a836c52 fix to the documentation link in create packages opening in same window 2019-09-02 10:17:19 +02:00
Kasper Christensen 24efa54481 Loginpage: Added an id to input fields and a for attr on the la… (#6247) 2019-09-02 10:00:44 +02:00
Mads Krohn 1bf85ab1db Correctly html encode text (#6235) 2019-09-02 08:46:33 +02:00
christophnz 1d94c4b9bb Fixed returntype of IPublishedContent.Siblings<T> to be IEnumerable<T> rather than IEnumerable<IPublishedContent> 2019-08-30 14:27:13 +02:00
Poornima Nayar 13e704e36c Fix- Unable to create members in the members section using the create option in the overlay 2019-08-30 08:50:15 +02:00
Bjarne Fyrstenborg 413b266335 v8: Replace tree loader with umbLoader component (#6169) 2019-08-29 14:36:51 +02:00
Kenn Jacobsen 7cb0886b35 Prevent markdown editor from stealing input focus on enter 2019-08-29 14:32:40 +02:00
Kenn Jacobsen d0e7344e2d Prevent double click errors when editing compositions 2019-08-29 14:30:10 +02:00
Andy Butland 8fdc14f360 Added metadata indicating if document published for content picker display 2019-08-29 14:27:31 +02:00
Sean McLemon 915b24e4e4 When deciding whether to use SSL, ApplicationUrlHelper.GetApplicationUrlFromCurrentRequest() has an expression port == "443" which is always false. The variable port is set to to either "" or ":" + request.ServerVariables["SERVER_PORT"], so it might sometimes be ":443" but it will never be just "443". This code has been this way for at least a year, and I don't know if the situation it tried to handle makes any sense (using port 443 over plain HTTP, for some reason??) so I've just removed it. 2019-08-29 14:26:16 +02:00
Callum Whyte a24e5bd10c Correcting ASCII art in README (#6156) 2019-08-29 14:22:21 +02:00
Kenn Jacobsen 3457018fb4 Don't use a hardcoded "image" type in media picker value conversion 2019-08-29 14:20:03 +02:00
Kenn Jacobsen bfc87922cb Make sure the empty search result is visible for list views 2019-08-29 14:13:43 +02:00
Steve Megson 09def9f352 Migration of MarkdownEditor prevalues 2019-08-29 14:12:32 +02:00
Steve Megson 934e2066a0 #6158 - missing media folders after migration 2019-08-29 14:09:19 +02:00
stevemegson b254b4fcda V8: Indexing of grid values containing JSON (#6201) 2019-08-29 14:06:59 +02:00
jmayntzhusen 4dad427a64 Improve error messages (#6154) 2019-08-29 14:01:30 +02:00
Steve Megson 9e1424fb24 RefactorMacroColumns 2019-08-29 13:58:43 +02:00
Kenn Jacobsen ad978c60df V8: Use a standard confirm dialog when removing content type compositions (#6213) 2019-08-29 13:51:55 +02:00
Kenn Jacobsen dfe96dccde Use a pointer cursor for selectable items in mini listviews 2019-08-29 13:50:37 +02:00
Poornima Nayar 981519eac0 Added contribution guidelines file
which was otherwise leading to a 404 from the Contributing.md file (#6197)
2019-08-29 13:49:26 +02:00
Kenn Jacobsen 16ab7b70cf V8: Add confirmation when deleting content and media (#6005) 2019-08-29 13:44:38 +02:00
Sebastiaan Janssen b2093e6dfa Merge branch 'v7/dev' into v8/dev
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.controller.js
#	src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html
#	src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js
#	src/Umbraco.Web.UI.Client/src/views/content/delete.html
#	src/Umbraco.Web.UI.Client/src/views/media/delete.html
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js
#	src/Umbraco.Web.UI/umbraco/config/lang/da.xml
#	src/Umbraco.Web.UI/umbraco/config/lang/en.xml
#	src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
2019-08-29 13:44:09 +02:00
Kenn Jacobsen 3f8dffee3f V7: Add confirmation when deleting content and media (#6004) 2019-08-29 13:41:34 +02:00
Kenn Jacobsen 204a9c9d53 Make sure Nested Content blueprints are listed in defined sort order 2019-08-29 13:37:45 +02:00
Kenn Jacobsen 531dee1e75 Ensure correct icon for file types in search results 2019-08-29 13:34:01 +02:00
Sebastiaan Janssen 297e054381 Merge branch 'v7/dev' into v8/dev
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js
#	src/Umbraco.Web/Search/UmbracoTreeSearcher.cs
2019-08-29 13:33:42 +02:00
Kenn Jacobsen e9eecb2031 Ensure correct icon for file types in search results 2019-08-29 13:31:18 +02:00
Sebastiaan Janssen 4f289a222f Merge remote-tracking branch 'origin/v7/7.15' into v8/dev
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js
2019-08-29 13:30:14 +02:00
Matt Brailsford e92631c623 Fixed #5907 by reloading content on bulk publish / unpublish
Fixes #5907

(cherry picked from commit 93bb75511b)
2019-08-29 13:26:41 +02:00
Matt Brailsford 93bb75511b Fixed #5907 by reloading content on bulk publish / unpublish
Fixes #5907
2019-08-29 13:25:56 +02:00
Sebastiaan Janssen 5d9e1087f4 Cherry pick 851a2a3de8 2019-08-29 13:03:48 +02:00
Sebastiaan Janssen 0308c8096e Merge branch 'v7/dev' into v8/dev
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html
2019-08-29 12:59:56 +02:00
Sebastiaan Janssen 02469f110f Merge branch 'v7/7.15' into v7/dev 2019-08-29 12:58:36 +02:00
Warren Buckley 851a2a3de8 Simplify svg check and make it a proper a href again. 2019-08-29 12:56:53 +02:00
Sebastiaan Janssen 74ccc34124 Merge branch 'v7/dev' into v8/dev
# Conflicts:
#	src/Umbraco.Core/Persistence/Factories/MediaFactory.cs
2019-08-29 12:38:35 +02:00
Cody Boucher f5a445101d use SystemDirectories.Media instead of hardcoded string media
(cherry picked from commit 2cdeb38521)
2019-08-29 12:34:21 +02:00
Cody Boucher 2cdeb38521 use SystemDirectories.Media instead of hardcoded string media 2019-08-29 12:33:31 +02:00
Sebastiaan Janssen b3834c296b Merge branch 'v7/dev' into v8/dev
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js
2019-08-29 11:57:50 +02:00
Sebastiaan Janssen 198b74f4de Merge branch 'v7/7.15' into v7/dev 2019-08-29 11:57:00 +02:00
Sebastiaan Janssen 3c7580ff7b Default for ignoreUserStartNodes should be false 2019-08-29 11:55:43 +02:00
Sebastiaan Janssen c0ad292ebb Merge branch 'v7/7.15' into v7/dev 2019-08-29 11:48:52 +02:00
Steve Megson 4ef55aa7d0 Multi Url Picker fix
(cherry picked from commit 3dc0bbeff3)
2019-08-29 11:48:04 +02:00
Steve Megson 3dc0bbeff3 Multi Url Picker fix 2019-08-29 09:34:35 +02:00
Warren Buckley b205f19f40 Merge pull request #6130 from stevemegson/v8/temp-6060
V8: Handle missing grid values in migration from 7.15
2019-08-27 14:31:52 +00:00
Warren Buckley 727c32843c Merge branch 'v8/8.1' into v8/dev
# Conflicts:
#	src/SolutionInfo.cs
2019-08-27 11:53:52 +01:00
Warren Buckley 78a63b20c7 Merge pull request #6120 from umbraco/v8/bugfix/6099-member-password-reset
Fixes: Member password reset checkbox doesn't hide "new" and "confirm" password fields. #6099
2019-08-27 10:48:27 +00:00
Claus c3e99205de fixed typo. 2019-08-26 13:29:54 +02:00
Claus 66ba6f51da Merge pull request #6147 from umbraco/v8/feature/2196-improve-tiny-load
Improve JS Asset load of TinyMCE Editor
2019-08-26 13:25:34 +02:00
elitsa 8625c8da43 Add special checks and preview for svg files media files for v8
(cherry picked from commit ac4128d89e)
2019-08-23 12:08:03 +01:00
Warren Buckley d86db3fc11 Merge pull request #6182 from umbraco/v8/bugfix/2180-secure-svg-preview
Secure svg media files preview - v8
2019-08-23 11:06:20 +00:00
elitsa ac4128d89e Add special checks and preview for svg files media files for v8 2019-08-23 10:42:00 +02:00
Steve Megson 11f2855442 Move CreateRoutes from WebFinalComponent to WebInitialComponent 2019-08-23 09:11:41 +02:00
Kenn Jacobsen d09be604d9 Allow plugin "Lang" folders to reside in nested directories 2019-08-23 08:56:42 +02:00
Sebastiaan Janssen d8355c368f Too many weird versions 2019-08-22 23:40:24 +02:00
stevemegson 703092c12c V8: Fix caching of macro results in RTE (#6010)
(cherry picked from commit 9b212121e3)
2019-08-22 23:37:06 +02:00
stevemegson 9b212121e3 V8: Fix caching of macro results in RTE (#6010) 2019-08-22 23:34:33 +02:00
Sebastiaan Janssen 88145d70fc Update nestedcontent.editor.html
forgot the "property-" prefix.

(cherry picked from commit 0aaf284f7f)
(cherry picked from commit d2649e9354)

# Conflicts:
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html
(cherry picked from commit ac6e399847)
2019-08-22 22:46:11 +02:00
Sebastiaan Janssen 320cc48132 Merge branch 'v7/dev' into v8/dev
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html
2019-08-22 22:44:05 +02:00
Sebastiaan Janssen ac6e399847 Update nestedcontent.editor.html
forgot the "property-" prefix.

(cherry picked from commit 0aaf284f7f)
(cherry picked from commit d2649e9354)

# Conflicts:
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html
2019-08-22 22:43:17 +02:00
Sebastiaan Janssen d2649e9354 Update nestedcontent.editor.html
forgot the "property-" prefix.

(cherry picked from commit 0aaf284f7f)
2019-08-22 22:31:36 +02:00
B. Gunnarsson 0aaf284f7f Update nestedcontent.editor.html
forgot the "property-" prefix.
2019-08-22 22:21:43 +02:00
Brynjólfur Gunnarsson a21c9420b1 added data element to nestedcontent.editor.html, tested it and it works" 2019-08-22 22:21:43 +02:00
Sebastiaan Janssen b396cc340c Cherry pick - Added trck by $index to handle duplicate names in blueprints 2019-08-22 22:06:57 +02:00
Sebastiaan Janssen 4b9ce6bb10 Merge branch 'v7/dev' into v8/dev
# Conflicts:
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Configuration/UmbracoVersion.cs
#	src/Umbraco.Core/DateTimeExtensions.cs
#	src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js
#	src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html
#	src/Umbraco.Web.UI.Client/src/views/content/create.html
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html
#	src/Umbraco.Web/Profiling/WebProfilerProvider.cs
#	src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs
2019-08-22 22:05:14 +02:00
Nik 55d0ca2557 Added trck by $index to handle duplicate names in blueprints
(cherry picked from commit d57ebf021c)
2019-08-22 21:51:58 +02:00
Nik d57ebf021c Added trck by $index to handle duplicate names in blueprints 2019-08-22 21:46:38 +02:00
Kenn Jacobsen a2899b21b6 Don't crash the linkpicker if only an anchor has been entered
(cherry picked from commit 1089b4b9bb)
2019-08-22 21:35:17 +02:00
Kenn Jacobsen 1089b4b9bb Don't crash the linkpicker if only an anchor has been entered 2019-08-22 21:04:12 +02:00
swcs 2428af3c40 Ability to distinguish document types with same name in Nested Content config (#6092) 2019-08-22 10:46:28 +02:00
Sebastiaan Janssen ffd5d61f9f Merge branch 'v7/7.15' into v7/dev 2019-08-21 15:25:54 +02:00
Lars-Erik Aabech 5b1f2ef95d V7: Fixes #6132, dates being blanked out on save/publish when user locale (momen… (#6138) 2019-08-21 15:21:31 +02:00
Sebastiaan Janssen 08c3b6bff5 Fix unit tests
(cherry picked from commit 82e11d7e0a)
2019-08-21 13:02:57 +01:00
Sebastiaan Janssen ccc2a0e811 Fix #5335 - Multiple Media Picker not available as macro parameter
(cherry picked from commit 1f2c4e38ac)
2019-08-21 13:02:36 +01:00
Warren Buckley 445e6d41b7 Merge pull request #6149 from umbraco/v8/feature/5335-multi-media-picker-parameter-editor
Fix #5335 - Multiple Media Picker not available as macro parameter
2019-08-21 11:15:41 +00:00
Sebastiaan Janssen 82e11d7e0a Fix unit tests 2019-08-21 12:45:06 +02:00
Elitsa Marinovska 2cc5256c00 Fixed typo in "ng-container"
(cherry picked from commit 7a7adfb61f)
2019-08-21 11:09:41 +01:00
elitsa 6f83d3c865 Add special checks and preview for svg files media files.
(cherry picked from commit defcb727f9)
2019-08-21 11:09:19 +01:00
Warren Buckley 3325627fd2 Merge pull request #6137 from umbraco/v7/bugfix/2180-secure-svg-preview
Secure svg media files preview
2019-08-21 10:07:08 +00:00
Warren Buckley bec54a0467 Remove unecessary change 2019-08-20 11:28:29 +01:00
Warren Buckley 4fc265275b Fix up unit test for RTE, it was failing as I added the Angular app.value for tinyMceAssets - this ensures we add an empty array of tinymce assets 2019-08-20 11:28:02 +01:00
Warren Buckley a95360036d Pre-loading this lang JS file does not seem to help Tiny as it does not check its already loaded & it reloads itself anyway (But the plugins work all fine & are checked by Tiny to see if they have been loaded) 2019-08-20 11:12:20 +01:00
Warren Buckley 6873c12f04 Update to main controller to load in the TinyMCE assets - assetsService deals with de-duping already loaded assets 2019-08-20 11:11:29 +01:00
Warren Buckley 0daa80efa4 Undo some of the changes as we will load it via assetService in the angular main controller & fixes the double load issue 2019-08-20 11:01:45 +01:00
Sebastiaan Janssen 1f2c4e38ac Fix #5335 - Multiple Media Picker not available as macro parameter 2019-08-19 16:01:03 +02:00
Warren Buckley b9b4721295 * Adds TinyMCE JS assets into init page load via script in default.cshtml view for Umbraco backoffice ng-app
* Add TinyMCE JS assets array via NG app.value & uses assetsService to load (if not loaded in time by main init script above)
2019-08-19 14:36:56 +01:00
Elitsa Marinovska 7a7adfb61f Fixed typo in "ng-container" 2019-08-17 23:54:49 +02:00
elitsa defcb727f9 Add special checks and preview for svg files media files. 2019-08-16 13:34:25 +02:00
Bjarne Fyrstenborg e4f19f7192 v8: Add umbLoader directive (#4987) 2019-08-16 11:52:34 +02:00
Kenn Jacobsen 0e565ddbf8 Make sure treepicker URLs are displayed for the current editor culture
(cherry picked from commit 438133ae1f)
2019-08-16 08:44:54 +02:00
Kenn Jacobsen 438133ae1f Make sure treepicker URLs are displayed for the current editor culture 2019-08-16 08:43:31 +02:00
Shannon Deminick 7ef7b23e93 Merge pull request #6121 from umbraco/v8/bugfix/5804-migration-fix
Fixes: v8.1 migration from v7 results in an error due to a unique index on cmsContent #5804
2019-08-16 16:20:24 +10:00
Shannon 6e1314ae30 Removing legacy service 2019-08-16 15:58:13 +10:00
Shannon Deminick 75ff3b26c1 Merge pull request #6125 from umbraco/v7/bugfix/2179-remove-legacy-web-service
Removing legacy service
2019-08-16 15:49:50 +10:00
Shannon 505b541040 Removing the MiniProfiler routes if solution is not in debug mode. 2019-08-16 15:39:46 +10:00
Shannon fbc12b607d Merge branch 'v7/7.15' into v7/dev 2019-08-16 15:38:19 +10:00
Shannon Deminick cb7440bbbf Merge pull request #6114 from umbraco/v7/bug/2178-disable-mini-profiler
Disable Mini Profiler
2019-08-16 15:37:05 +10:00
Shannon cd355910b3 strangely i need the fully qualified name here else i get an error, otherwise works great. 2019-08-16 15:34:49 +10:00
Andy Butland 5452ebaf6d Import and export of document type and property variations setting in packages 2019-08-15 21:57:09 +02:00
Marc Goodson cec4376d19 Update GetPagedResultsByQuery to return id of the node the audit item relates to instead of table PK
This was fixed previously in v7 - see https://github.com/umbraco/Umbraco-CMS/pull/3759

(cherry picked from commit 53c52abde5)
2019-08-15 21:19:38 +02:00
Marc Goodson 53c52abde5 Update GetPagedResultsByQuery to return id of the node the audit item relates to instead of table PK
This was fixed previously in v7 - see https://github.com/umbraco/Umbraco-CMS/pull/3759
2019-08-15 21:18:41 +02:00
skttl 0d63b13bf0 Changes mode of styleselect to All 2019-08-15 21:17:14 +02:00
Kenn Jacobsen 65b2d08997 Set media picker dirty when the value changes
(cherry picked from commit 2b7154622e)
2019-08-15 21:12:57 +02:00
Kenn Jacobsen 2b7154622e Set media picker dirty when the value changes 2019-08-15 21:12:12 +02:00
Bjarne Fyrstenborg 0baf5eab0a v8: Rename datetime picker component (#6110) 2019-08-15 21:09:58 +02:00
Kenn Jacobsen 16ac2c5589 Don't change parent protection when editing child protection
(cherry picked from commit 4743fc0e1d)
2019-08-15 21:07:19 +02:00
Kenn Jacobsen 4743fc0e1d Don't change parent protection when editing child protection 2019-08-15 21:06:21 +02:00
Kenn Jacobsen f8409be506 Use an Umbraco confirm dialog when deleting groups
(cherry picked from commit f1f9e1742e)
2019-08-15 21:05:04 +02:00
Kenn Jacobsen f1f9e1742e Use an Umbraco confirm dialog when deleting groups 2019-08-15 21:04:33 +02:00
Kenn Jacobsen 455395154a Don't reuse previously used content templates
(cherry picked from commit 3087eff147)
2019-08-15 21:01:45 +02:00
Kenn Jacobsen 3087eff147 Don't reuse previously used content templates 2019-08-15 21:00:22 +02:00
Sebastiaan Janssen 8e2eccb392 Bump version to 7.15.2 2019-08-15 13:32:09 +02:00
Sebastiaan Janssen a0af474c75 Bump version to 8.1.3 2019-08-15 13:29:14 +02:00
Kenn Jacobsen 7e8364eae4 V8: Support mixed element variance in Nested Content (#5952)
(cherry picked from commit e7a9ee8142)
2019-08-15 13:27:50 +02:00
Kenn Jacobsen e7a9ee8142 V8: Support mixed element variance in Nested Content (#5952) 2019-08-15 13:10:17 +02:00
Steve Megson 806d835a90 Handle null and missing grid values in migration 2019-08-14 21:14:48 +01:00
Jan Skovgaard 617fd9ab17 Accessibility: Open Package - Identifies back button as "Left h… (#5930) 2019-08-14 17:47:13 +02:00
Jan Skovgaard fe6df58e66 Accessibility: Media uploader widget can't be accessed via keyb… (#5944) 2019-08-14 17:40:52 +02:00
Jan Skovgaard 6bd647f8c6 Accessibility: Unable to navigate to close 'X' icon with keyboa… (#5964) 2019-08-14 17:29:26 +02:00
Jan Skovgaard 8d0df6cec3 Accessibility: Open User Details - Back icon arrow is identifie… (#5970) 2019-08-14 17:21:56 +02:00
Jan Skovgaard 5997e0ce8c Accessibility: Ensure "cancel" is clickable from move/restore d… (#5942) 2019-08-14 17:11:02 +02:00
Kenn Jacobsen 8bdd694a12 V8: Make sure to mark the current form as dirty when altering content types (#6108) 2019-08-14 16:55:32 +02:00
elitsa ee2068de56 Removing legacy service 2019-08-14 14:03:32 +02:00
Shannon dfed93a4ab Fixes dropping unique constraint indexes and oddly named FKs 2019-08-14 14:49:05 +10:00
Shannon 4e17463eb8 Merge branch 'v8/8.1' into v8/dev 2019-08-14 12:52:07 +10:00
Shannon df391c86f3 Fixes re-syncing member and media data after saving 2019-08-14 12:45:39 +10:00
Shannon 67f0a65811 Merge branch 'v8/8.1' into v8/bugfix/6099-member-password-reset 2019-08-14 12:30:02 +10:00
Shannon 9d77114d50 fixes gulp build for dev purposes - no fastdev since there's no need for it, removes inlining templates when not in prod 2019-08-14 12:28:28 +10:00
Shannon d2be30a228 converting to component 2019-08-14 12:26:49 +10:00
Shannon 9ed1101f27 whitespace changes 2019-08-14 11:16:24 +10:00
Shannon f60b91110f Fixes merge 2019-08-14 10:56:08 +10:00
Shannon db6714d3b8 Merge remote-tracking branch 'origin/v8/8.1' into v8/dev
# Conflicts:
#	src/SolutionInfo.cs
#	src/Umbraco.Web/Search/ExamineComponent.cs
2019-08-14 10:39:15 +10:00
Sebastiaan Janssen 47c8de4ca1 Merge branch 'v7/dev' into v8/dev
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
2019-08-13 23:29:58 +02:00
Matt Brailsford 802e6d7979 Fixes #5931, show breadcrumb for new content
Could probably do with adding "Untitled" to translation file

(cherry picked from commit 249126a2b5)
2019-08-13 23:17:06 +02:00
Matt Brailsford 249126a2b5 Fixes #5931, show breadcrumb for new content
Could probably do with adding "Untitled" to translation file
2019-08-13 23:16:33 +02:00
Matt Brailsford ea591c87a2 Fixes #5932, showing "Return to ListView" for new content
(cherry picked from commit f3fc83fae6)
2019-08-13 23:11:01 +02:00
Matt Brailsford f3fc83fae6 Fixes #5932, showing "Return to ListView" for new content 2019-08-13 23:09:17 +02:00
BatJan d005af2b11 Remove unused visuallyhidden class 2019-08-13 22:59:49 +02:00
Kenn Jacobsen 3b60815e53 Fix user group deletion (#5912)
(cherry picked from commit b0b05cf785)
2019-08-13 22:55:12 +02:00
Kenn Jacobsen b0b05cf785 Fix user group deletion (#5912) 2019-08-13 22:54:19 +02:00
Bjarne Fyrstenborg d5d213cd44 Allow tree item label to shrink
(cherry picked from commit 2d56879d92)
2019-08-13 22:46:31 +02:00
Bjarne Fyrstenborg 2d56879d92 Allow tree item label to shrink 2019-08-13 22:45:09 +02:00
Sebastiaan Janssen 13d07c7ba3 Merge branch 'v7/dev' into v8/dev
# Conflicts:
#	src/Umbraco.Web/Editors/MediaTypeController.cs
2019-08-13 22:43:30 +02:00
Tom Pipe ece7178ef5 Ensure a media type correctly inherits it's selected parent
(cherry picked from commit ab63d2664c)
2019-08-13 22:41:01 +02:00
Tom Pipe ab63d2664c Ensure a media type correctly inherits it's selected parent 2019-08-13 22:40:33 +02:00
Tom Pipe f829ac4e6c Ensure a media type correctly inherits it's selected parent
(cherry picked from commit 28dd9aa081)
2019-08-13 22:38:39 +02:00
Tom Pipe 28dd9aa081 Ensure a media type correctly inherits it's selected parent 2019-08-13 22:37:59 +02:00
Sebastiaan Janssen eb8556c792 Merge branch 'v7/dev' into v8/dev 2019-08-13 22:26:16 +02:00
Sebastiaan Janssen 84e4c78bb9 Merge branch 'v7/7.15' into v7/dev 2019-08-13 22:24:21 +02:00
arkadiuszbiel 6123c0d475 Change z-index
(cherry picked from commit 3fa77dbf3d)
(cherry picked from commit 12ad9dd36b)

# Conflicts:
#	src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less
2019-08-13 22:20:52 +02:00
arkadiuszbiel 12ad9dd36b Change z-index
(cherry picked from commit 3fa77dbf3d)
2019-08-13 22:16:42 +02:00
Sebastiaan Janssen ba040674cb Merge branch 'v7/dev' into v8/dev
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less
2019-08-13 22:15:57 +02:00
arkadiuszbiel 3fa77dbf3d Change z-index 2019-08-13 22:14:53 +02:00
Sebastiaan Janssen 9fa435dbb2 Merge branch 'v7/dev' into v8/dev
# Conflicts:
#	src/Umbraco.Core/Services/NotificationService.cs
#	src/Umbraco.Web.UI.Client/src/less/tree.less
#	src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js
#	src/Umbraco.Web/Properties/Settings.settings
#	src/Umbraco.Web/Properties/Settings1.Designer.cs
2019-08-13 22:11:34 +02:00
Nathan Woulfe 02c1e88da2 V8: Display current log level in log viewer sidebar (#6059) 2019-08-13 22:07:05 +02:00
Kenn Jacobsen 87e60ef26b V8: Support hexa in hexBgColor (#6091) 2019-08-13 21:56:11 +02:00
Kenn Jacobsen 87845ee907 V8: Allow localization of the backoffice using parent cultures… (#6090)
(cherry picked from commit 405538544f)
2019-08-13 20:28:48 +02:00
Kenn Jacobsen 405538544f V8: Allow localization of the backoffice using parent cultures… (#6090) 2019-08-13 20:26:22 +02:00
elitsa afca2e70bd Removing the MiniProfiler routes if solution is not in debug mode. 2019-08-13 14:17:14 +02:00
Peter Gregory 31d67161ab V8: Implementation of Create method for infinite editing edtior… (#6031) 2019-08-13 08:07:53 +02:00
Claus 66e3869b06 Merge pull request #6080 from umbraco/v8/feature/2190-improve-jarring-tiny-loading
AB#2190 - Improve jarring TinyMCE editor loading experience
2019-08-12 11:54:27 +02:00
swcs 600be987a7 use modifiable media path in file system composition
(cherry picked from commit fdcfc48e06)
2019-08-12 09:05:45 +02:00
swcs fdcfc48e06 use modifiable media path in file system composition 2019-08-12 09:04:32 +02:00
Shannon eb46b16800 subtle mistake in gulp change 2019-08-12 16:18:49 +10:00
Shannon Deminick 392445b53a Merge pull request #6086 from umbraco/v8/feature/more-flexi-angular-sln
Fix the gulp build to allow directives/components to exist where they need to be allowed
2019-08-12 09:36:25 +10:00
Kenn Jacobsen 7c93b8f157 Use mculture in content editor for multilingual content
(cherry picked from commit 2f84e76952)
2019-08-09 16:59:33 +02:00
Kenn Jacobsen 9865596dcc V7: Change the priority of the tree node annotations for contai… (#6083)
(cherry picked from commit 7393d9c372)
2019-08-09 16:47:14 +02:00
Kenn Jacobsen 7393d9c372 V7: Change the priority of the tree node annotations for contai… (#6083) 2019-08-09 16:44:43 +02:00
Kenn Jacobsen c87a22f347 Fix the disabled upload button in media picker (#6082)
(cherry picked from commit feb5567621)
2019-08-09 16:42:57 +02:00
Kenn Jacobsen dc44a7a3a7 Fix the disabled upload button in media picker
(cherry picked from commit 79c4d7797c)
2019-08-09 16:41:13 +02:00
Kenn Jacobsen 79c4d7797c Fix the disabled upload button in media picker 2019-08-09 16:40:08 +02:00
Rachel Breeze 9cab789425 V8: Accessibility improvements for page name entry (#5760)
(cherry picked from commit ab3202b560)
2019-08-09 16:27:02 +02:00
Kenn Jacobsen a0cd542ba3 Fix "infinityMode" typo
(cherry picked from commit e10ce234db)
2019-08-09 16:00:54 +02:00
Shannon 7f02bc7d1d fix controller glob 2019-08-09 16:55:58 +10:00
Shannon 44b6738b0d Moves main/navigation controllers to root - no more 'controller' folder 2019-08-09 16:36:10 +10:00
Shannon fbb19cba0b Updates the build so we can be more flexible with where we put angular file types in the solution 2019-08-09 16:02:05 +10:00
Peter Keating ff595df7ca Fix notifications not sending when user has multiple of same type
When a user has multiple notifications of the same type, the user may
not receive the notification depending on the order which the
notification was setup. To fix this the notification logic has changed
from looping over users to looping over notifications.

Fixes #4713.

(cherry picked from commit 01a895bbf7)
2019-08-08 19:49:59 +02:00
Peter Keating 01a895bbf7 Fix notifications not sending when user has multiple of same type
When a user has multiple notifications of the same type, the user may
not receive the notification depending on the order which the
notification was setup. To fix this the notification logic has changed
from looping over users to looping over notifications.

Fixes #4713.
2019-08-08 19:48:39 +02:00
Steve Megson 88349c6b66 Sorting of images in multiple media picker
(cherry picked from commit ed5effd190)
2019-08-08 16:02:35 +02:00
Warren Buckley c6671f7f8e Apply same loading bar UI to Grid RTE & use editor INIT to hide/remove it 2019-08-08 12:35:39 +01:00
Warren Buckley 44c966bd44 Moves our Umbraco angular loader component to only hide itself once the tinymce editor has initialized 2019-08-08 11:37:18 +01:00
Sebastiaan Janssen 046ed8ec85 Bump version to 8.1.2 2019-08-08 12:23:57 +02:00
Steve Megson aabe6defb2 Fixes #5864 - migrate prevalues for Umbraco.DropDown.Flexible
(cherry picked from commit 4e0dd728a8)
2019-08-08 10:56:02 +02:00
Bjarke Berg 3245c82ea6 Missing file in project file
(cherry picked from commit 3062f40ef5)
2019-08-08 10:45:34 +02:00
Kenn Jacobsen beacc5bba8 Fix broken button to open content type from create dialog
(cherry picked from commit 57e58c6cf7)
2019-08-08 10:05:33 +02:00
leekelleher 7ffe0398f9 DocType Editor sorting - vertical direction
In the DocType Editor, when sorting the groups and properties,
the draggable direction is freeform (horizontal, vertical or even
diagonal).

We can restrict the direction, by setting the `axis` property in
the sort options.
2019-08-07 20:55:35 +02:00
Kenn Jacobsen ce5b3531e0 Make content types with description look ok in the create dialog (#5752)
(cherry picked from commit b3497011e8)
2019-08-07 20:50:08 +02:00
Sebastiaan Janssen a9a5d00916 Merge pull request #5999 from kjac/v8-fix-allow-remove-user-start-nodes
V8: Allow removing user start nodes
(cherry picked from commit 2cf3a7831c)
2019-08-07 20:25:44 +02:00
Gopakumar R 62a71f8052 Fix for issue #5816 (#5836)
(cherry picked from commit 0138091a6c)
2019-08-07 20:16:50 +02:00
Kenn Jacobsen 36eccb472c Fix the create context menu overflows introduced with 405e9a79 (#5754)
(cherry picked from commit 6c0fb2f849)
2019-08-07 19:58:13 +02:00
Benjamin Carleski 289d50d170 Fix FIPS compliance, add FIPS tests (#5978)
(cherry picked from commit e495faf83c)
2019-08-07 19:34:13 +02:00
Linus Elander 0517f29134 Adjusts conditions for which indexes should be affected when a node is unpublished
(cherry picked from commit 7557c584d8)
2019-08-07 16:38:13 +02:00
Shannon 467cf8d762 Adds comments/notes 2019-08-07 15:40:27 +10:00
Bjarke Berg 2cdb6d86ad Merge pull request #6019 from umbraco/v8/bugfix/pending-changes-issue
v8 Changing a property type from invariant to variant and back again causes issues with reporting that a document is edited or not
2019-08-06 09:09:12 +02:00
Bjarke Berg d474f60465 Merge pull request #5992 from umbraco/v8/bugfix/5554-unpublish-state
V8/bugfix/5554 unpublish state
2019-08-05 11:54:49 +02:00
Bjarke Berg caf4d1fe34 Merge pull request #6038 from umbraco/v8/bugfix/examine-startup
Fixes how examine starts up to make it easier for custom index developers
2019-08-05 10:39:29 +02:00
Bjarke Berg e4f346620e Added runtime level to ExamineFinalComposer 2019-08-05 10:38:35 +02:00
Shannon be4536cf16 Refactors ugly statics and nested classes 2019-08-05 17:14:29 +10:00
Shannon 89429bd65a fixes ctor 2019-08-05 16:28:23 +10:00
Shannon 6d3faf91d8 fixes spacing with ext login providers 2019-08-05 13:50:50 +10:00
Shannon 345e6091d2 fixes null check, was on the wrong element 2019-08-02 13:01:02 +10:00
Shannon 86a97c30d1 fixes up LuceneIndexDiagnostics 2019-08-02 12:40:45 +10:00
Shannon 14915e9da7 oops 2019-08-02 01:16:37 +10:00
Shannon 366058581a Adds LuceneIndexDiagnostics to make it easier for devs and custom indexes 2019-08-02 01:15:08 +10:00
Shannon 9f6a7dec2e Fixes how examine starts up to make it easier for custom index developers 2019-08-02 00:28:48 +10:00
Shannon be30bb0ad0 adds test 2019-08-01 21:41:43 +10:00
Shannon 600a29514f more asserts 2019-08-01 21:27:31 +10:00
Shannon 9efec9244b removes commented out code 2019-08-01 19:09:19 +10:00
Shannon ded1a22e45 fixes validation check 2019-08-01 19:01:41 +10:00
Shannon 2e83ac9282 Cleans up some tests 2019-08-01 18:54:19 +10:00
Shannon 9538981730 Adds more assertions and tests and validates variations 2019-08-01 18:49:05 +10:00
Shannon 791222c3ce Merge remote-tracking branch 'origin/v8/8.1' into v8/bugfix/5554-unpublish-state 2019-08-01 16:11:16 +10:00
Shannon 1095815878 removes notes 2019-08-01 16:09:52 +10:00
Shannon a8bdab8f5c Merge remote-tracking branch 'origin/v8/8.1' into v8/bugfix/pending-changes-issue 2019-08-01 15:56:24 +10:00
Shannon ff952a6df1 Passes test! Now to add more tests/assertions and then the logic to renormalize based on name changes. 2019-08-01 15:54:44 +10:00
Shannon 87e7cec02e WIP - commiting what i have. Solved part of the problem but there are others. 2019-07-31 18:30:34 +10:00
Shannon 5c8cd60275 Writes up a test to show the issue with edited culture flags, this shows that the current fix works but there's still an issue 2019-07-31 00:13:40 +10:00
Bjarke Berg e2483dc874 Merge pull request #6020 from umbraco/v8/bugfix/panic-exception
Adds PanicException with details instead of throwing "panic" strings
2019-07-30 15:22:05 +02:00
Shannon 6482d849e3 Moves test to different fixture 2019-07-30 22:59:33 +10:00
Shannon 34f137c8cf Merge branch 'v8/bugfix/panic-exception' into v8/bugfix/5554-unpublish-state 2019-07-30 22:50:06 +10:00
Shannon 16d8f8846e Adds PanicException with details instead of throwing "panic" strings 2019-07-30 22:40:15 +10:00
Shannon 108416638c removes commented out code 2019-07-30 22:28:06 +10:00
Shannon 20d28ea215 removes commented out code 2019-07-30 22:20:28 +10:00
Shannon 27bc309c55 Removes unused code 2019-07-30 22:20:28 +10:00
Shannon b5b4ee79b4 Adds notes, fixes logic with a double save 2019-07-30 22:20:28 +10:00
Shannon 45227357fd adds notes on why this is failing 2019-07-30 22:20:28 +10:00
Shannon a2ba0f6a4c more tests - failing that need fixing 2019-07-30 22:20:27 +10:00
Shannon 415916c527 Fixes an issue with the content service + test 2019-07-30 22:20:27 +10:00
Shannon 40a55cb9df Fixes issue/tests 2019-07-30 22:20:27 +10:00
Shannon 5a9ca8d09f wip added more tests and notes 2019-07-30 22:20:27 +10:00
Shannon 1bfcc9053e Fixes issues: we never properly used SuccessUnpublishMandatoryCulture, fixed issue of unpublishing last culture with a new status 2019-07-30 22:20:27 +10:00
Shannon 3930ae244c Created unit test to show the problem. 2019-07-30 22:20:27 +10:00
Shannon 365ad2981d Fixing issue when changing a property type from invariant to variant and back again and the document always reporting pending changes. 2019-07-30 19:02:03 +10:00
Rasmus John Pedersen ca56655d07 Fix exception when stored media url is already absolute
(cherry picked from commit 49b3351c72)
2019-07-29 14:31:04 +02:00
Shannon 4612c03657 Fixes #5789 - PublishedContentQuery Search always searches on culture 2019-07-29 15:43:03 +10:00
Shannon 41093e5b8b Value Set builder shouldn't stop indexing when one field is empty in value sets 2019-07-29 15:38:08 +10:00
Shannon efd6bd82bd Revert "V8: Don't show multiple open menus (take two) (#5609)"
This reverts commit 9ce996cbba.

(cherry picked from commit 1d49c8626e)
2019-07-24 12:02:41 +02:00
Sebastiaan Janssen 17527c99eb Makes checks a little more robust
(cherry picked from commit ca75a25802)
2019-07-24 11:37:29 +02:00
Steve Megson 361557dc39 Handle CSV values in TagsPropertyEditor
(cherry picked from commit a97604d6c4)
2019-07-24 11:37:17 +02:00
Shannon 4fa523db43 Merge branch 'v7/7.15' into v7/dev 2019-07-24 19:23:58 +10:00
Shannon 186460ac08 removes refs from old packages.umbraco.org 2019-07-24 19:23:05 +10:00
Kenn Jacobsen 342b2087e9 Make sure save options are up to date with the content state
(cherry picked from commit 8a43e3a87e)
2019-07-24 10:59:01 +02:00
Steve Megson dc371c1979 Include CheckBoxList in ValueListPreValueMigrator 2019-07-23 12:48:48 +02:00
Bjarke Berg 2ec8281163 Merge pull request #5865 from skttl/5848-media-folders-not-showing
5848: changes innerjoin to left join, to allow folders in sql query
2019-07-16 07:24:46 +02:00
skttl d31e71a838 changes innerjoin to left join, to allow folders in sql query 2019-07-12 07:33:17 +02:00
208 changed files with 4193 additions and 1973 deletions
+35
View File
@@ -0,0 +1,35 @@
# Contributing to Umbraco CMS
When youre considering creating a pull request for Umbraco CMS, we will categorize them in two different sizes, small and large.
The process for both sizes is very similar, as [explained in the contribution document](CONTRIBUTING.md#how-do-i-begin).
## Small PRs
Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files.
Were usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process](REVIEW_PROCESS.md).
### Up for grabs
Umbraco HQ will regularly mark newly created issues on the issue tracker with the `Up for grabs` tag. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out.
If you do start working on something, make sure leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
## Large PRs
New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGets packages.config, NPMs packages.json, etc.).
We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We dont want to put up any barriers, but this document should set the correct expectations.
Please make sure to describe your idea in an issue, it helps to put in mockup screenshots or videos.
If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how wed like to see it being implemented.
If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the [small PRs process](#small-prs) above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. Well keep you posted on the progress.
It is highly recommended that you speak to the HQ before making large, complex changes.
### Pull request or package?
If it doesnt fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and to fix bugs.
Eventually, a package could "graduate" to be included in the CMS.
+25
View File
@@ -0,0 +1,25 @@
# Review process
You're an awesome person and have sent us your contribution in the form of a pull request! It's now time to relax for a bit and wait for our response.
In order to set some expectations, here's what happens next.
## Review process
You will get an initial reply within 48 hours (workdays) to acknowledge that weve seen your PR and well pick it up as soon as we can.
You will get feedback within at most 14 days after opening the PR. Youll most likely get feedback sooner though. Then there are a few possible outcomes:
- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco
- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible
- Your proposed change is awesome but needs a bit more work, well give you feedback on the changes wed like to see
- Your proposed change is awesome but.. not something were looking to include at this point. Well close your PR and the related issue (well be nice about it!)
## Are you still available?
We understand you have other things to do and can't just drop everything to help us out.
So if were asking for your help to improve the PR well wait for two weeks to give you a fair chance to make changes. Well ask for an update if we dont hear back from you after that time.
If we dont hear back from you for 4 weeks, well close the PR so that it doesnt just hang around forever. Youre very welcome to re-open it once you have some more time to spend on it.
There will be times that we really like your proposed changes and well finish the final improvements wed like to see ourselves. You still get the credits and your commits will live on in the git repository.
+7 -7
View File
@@ -1,11 +1,11 @@
888
888
888 888 88888b.d88b. 88888b. 888d888 8888b. .d8888b .d88b.
888 888 888 "888 "88b 888 "88b 888P" "88b d88P" d88""88b
888 888 888 888 888 888 888 888 .d888888 888 888 888
Y88b 888 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
"Y88888 888 888 888 88888P" 888 "Y888888 "Y8888P "Y88P"
888
888
888 888 88888b.d88b. 88888b. 888d888 8888b. .d8888b .d88b.
888 888 888 "888 "88b 888 "88b 888P" "88b d88P" d88""88b
888 888 888 888 888 888 888 888 .d888888 888 888 888
Y88 88Y 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
"Y888P" 888 888 888 88888P" 888 "Y888888 "Y8888P "Y88P"
------------------------------------------------------------------
+8 -7
View File
@@ -1,12 +1,13 @@
_ _ __ __ ____ _____ _____ ____
| | | | \/ | _ \| __ \ /\ / ____/ __ \
| | | | \ / | |_) | |__) | / \ | | | | | |
| | | | |\/| | _ <| _ / / /\ \| | | | | |
| |__| | | | | |_) | | \ \ / ____ | |___| |__| |
\____/|_| |_|____/|_| \_/_/ \_\_____\____/
888
888
888 888 88888b.d88b. 88888b. 888d888 8888b. .d8888b .d88b.
888 888 888 "888 "88b 888 "88b 888P" "88b d88P" d88""88b
888 888 888 888 888 888 888 888 .d888888 888 888 888
Y88 88Y 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
"Y888P" 888 888 888 88888P" 888 "Y888888 "Y8888P "Y88P"
----------------------------------------------------
------------------------------------------------------------------
Don't forget to build!
@@ -90,7 +90,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
// register the IFileSystem supporting the IMediaFileSystem
// THIS IS THE ONLY THING THAT NEEDS TO CHANGE, IN ORDER TO REPLACE THE UNDERLYING FILESYSTEM
// and, SupportingFileSystem.For<IMediaFileSystem>() returns the underlying filesystem
composition.SetMediaFileSystem(() => new PhysicalFileSystem("~/media"));
composition.SetMediaFileSystem(() => new PhysicalFileSystem(SystemDirectories.Media));
return composition;
}
@@ -96,7 +96,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
var pluginLangFolders = appPlugins.Exists == false
? Enumerable.Empty<LocalizedTextServiceSupplementaryFileSource>()
: appPlugins.GetDirectories()
.SelectMany(x => x.GetDirectories("Lang"))
.SelectMany(x => x.GetDirectories("Lang", SearchOption.AllDirectories))
.SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly))
.Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false));
+49 -18
View File
@@ -12,31 +12,62 @@
/// or MS.DI, PerDependency in Autofac.</remarks>
Transient,
// TODO: We need to fix this up, currently LightInject is the only one that behaves differently from all other containers.
// ... the simple fix would be to map this to PerScopeLifetime in LI but need to wait on a response here https://github.com/seesharper/LightInject/issues/494#issuecomment-518942625
//
// we use it for controllers, httpContextBase and other request scoped objects: MembershpHelper, TagQuery, UmbracoTreeSearcher and ISearchableTree
// - so that they are automatically disposed at the end of the scope (ie request)
// - not sure they should not be simply 'scoped'?
/// <summary>
/// One unique instance per request.
/// </summary>
// TODO: review lifetimes for LightInject vs other containers
// currently, corresponds to 'Request' in LightInject which is 'Transient + disposed by Scope'
// but NOT (in LightInject) a per-web-request lifetime, more a TransientScoped
//
// we use it for controllers, httpContextBase and umbracoContext
// - so that they are automatically disposed at the end of the scope (ie request)
// - not sure they should not be simply 'scoped'?
//
// Castle has an extra PerWebRequest something, and others use scope
// what about Request before first request ie during application startup?
// see http://blog.ploeh.dk/2009/11/17/UsingCastleWindsor'sPerWebRequestlifestylewithASP.NETMVConIIS7/
// Castle ends up requiring a special scope manager too
// see https://groups.google.com/forum/#!topic/castle-project-users/1E2W9LVIYR4
//
// but maybe also - why are we requiring scoped services at startup?
/// <remarks>
/// <para>
/// Any instance created with this lifetime will be disposed at the end of a request.
/// </para>
/// Corresponds to
/// <para>
/// PerRequestLifeTime in LightInject - means transient but disposed at the end of the current web request.
/// see: https://github.com/seesharper/LightInject/issues/494#issuecomment-518493262
/// </para>
/// <para>
/// Scoped in MS.DI - means one per web request.
/// see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes</para>
/// <para>
/// InstancePerRequest in Autofac - means one per web request.
/// see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request
/// But "Behind the scenes, though, its still just instance per matching lifetime scope."
/// </para>
/// <para>
/// LifestylePerWebRequest in Castle Windsor - means one per web request.
/// see https://github.com/castleproject/Windsor/blob/master/docs/mvc-tutorial-part-7-lifestyles.md#the-perwebrequest-lifestyle
/// </para>
/// </remarks>
Request,
/// <summary>
/// One unique instance per container scope.
/// One unique instance per scope.
/// </summary>
/// <remarks>Corresponds to Scope in LightInject, Scoped in MS.DI
/// or Castle Windsor, PerLifetimeScope in Autofac.</remarks>
/// <remarks>
/// <para>
/// Any instance created with this lifetime will be disposed at the end of the current scope.
/// </para>
/// Corresponds to
/// <para>PerScopeLifetime in LightInject (when in a request, means one per web request)</para>
/// <para>
/// Scoped in MS.DI (when in a request, means one per web request)
/// see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes</para>
/// <para>
/// InstancePerLifetimeScope in Autofac (when in a request, means one per web request)
/// see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-lifetime-scope
/// Also note that Autofac's InstancePerRequest is the same as this, see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request
/// it says "Behind the scenes, though, its still just instance per matching lifetime scope."
/// </para>
/// <para>
/// LifestyleScoped in Castle Windsor
/// </para>
/// </remarks>
Scope,
/// <summary>
@@ -96,6 +96,11 @@ namespace Umbraco.Core
/// </summary>
public const string MediaPicker = "Umbraco.MediaPicker";
/// <summary>
/// Multiple Media Picker.
/// </summary>
public const string MultipleMediaPicker = "Umbraco.MultipleMediaPicker";
/// <summary>
/// Member Picker.
/// </summary>
+2 -1
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
@@ -15,7 +16,7 @@ namespace Umbraco.Core
/// <returns></returns>
public static string ToIsoString(this DateTime dt)
{
return dt.ToString("yyyy-MM-dd HH:mm:ss");
return dt.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
}
public static DateTime TruncateTo(this DateTime dt, DateTruncate truncateTo)
@@ -0,0 +1,28 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Exceptions
{
/// <summary>
/// Internal exception that in theory should never ben thrown, it is only thrown in circumstances that should never happen
/// </summary>
[Serializable]
internal class PanicException : Exception
{
public PanicException()
{
}
public PanicException(string message) : base(message)
{
}
public PanicException(string message, Exception innerException) : base(message, innerException)
{
}
protected PanicException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}
@@ -42,6 +42,12 @@ namespace Umbraco.Core.Logging.Viewer
bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
/// <summary>
/// Gets the current Serilog minimum log level
/// </summary>
/// <returns></returns>
string GetLogLevel();
/// <summary>
/// Returns the collection of logs
/// </summary>
@@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using Newtonsoft.Json;
using Serilog;
using Serilog.Events;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Formatting = Newtonsoft.Json.Formatting;
namespace Umbraco.Core.Logging.Viewer
{
@@ -89,6 +92,16 @@ namespace Umbraco.Core.Logging.Viewer
return errorCounter.Count;
}
/// <summary>
/// Get the Serilog minimum-level value from the config file.
/// </summary>
/// <returns></returns>
public string GetLogLevel()
{
var logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Where(Log.Logger.IsEnabled)?.Min() ?? null;
return logLevel?.ToString() ?? "";
}
public LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod)
{
var counter = new CountingFilter();
+3 -2
View File
@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Exceptions;
namespace Umbraco.Core.Mapping
{
@@ -259,7 +260,7 @@ namespace Umbraco.Core.Mapping
if (typeof(TTarget).IsArray)
{
var elementType = typeof(TTarget).GetElementType();
if (elementType == null) throw new Exception("panic");
if (elementType == null) throw new PanicException("elementType == null which should never occur");
var targetArray = Array.CreateInstance(elementType, targetList.Count);
targetList.CopyTo(targetArray, 0);
target = targetArray;
@@ -382,7 +383,7 @@ namespace Umbraco.Core.Mapping
{
if (type.IsArray) return type.GetElementType();
if (type.IsGenericType) return type.GenericTypeArguments[0];
throw new Exception("panic");
throw new PanicException($"Could not get enumerable or array type from {type}");
}
/// <summary>
@@ -1,5 +1,7 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core;
using Umbraco.Core.Migrations.Expressions.Common;
using Umbraco.Core.Persistence.SqlSyntax;
@@ -27,31 +29,57 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.KeysAndIndexes
{
_context.BuildingExpression = false;
//get a list of all constraints - this will include all PK, FK and unique constraints
var tableConstraints = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToList();
//get a list of defined indexes - this will include all indexes, unique indexes and unique constraint indexes
var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToList();
var uniqueConstraintNames = tableConstraints.Where(x => !x.Item2.InvariantStartsWith("PK_") && !x.Item2.InvariantStartsWith("FK_")).Select(x => x.Item2);
var indexNames = indexes.Select(x => x.IndexName).ToList();
// drop keys
if (DeleteLocal || DeleteForeign)
{
// table, constraint
var tableKeys = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToList();
if (DeleteForeign)
{
foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("FK_")))
//In some cases not all FK's are prefixed with "FK" :/ mostly with old upgraded databases so we need to check if it's either:
// * starts with FK OR
// * doesn't start with PK_ and doesn't exist in the list of indexes
foreach (var key in tableConstraints.Where(x => x.Item1 == TableName
&& (x.Item2.InvariantStartsWith("FK_") || (!x.Item2.InvariantStartsWith("PK_") && !indexNames.InvariantContains(x.Item2)))))
{
Delete.ForeignKey(key.Item2).OnTable(key.Item1).Do();
}
}
if (DeleteLocal)
{
foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("PK_")))
foreach (var key in tableConstraints.Where(x => x.Item1 == TableName && x.Item2.InvariantStartsWith("PK_")))
Delete.PrimaryKey(key.Item2).FromTable(key.Item1).Do();
// note: we do *not* delete the DEFAULT constraints
// note: we do *not* delete the DEFAULT constraints and if we wanted to we'd have to deal with that in interesting ways
// since SQL server has a specific way to handle that, see SqlServerSyntaxProvider.GetDefaultConstraintsPerColumn
}
}
// drop indexes
if (DeleteLocal)
{
var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToList();
{
foreach (var index in indexes.Where(x => x.TableName == TableName))
Delete.Index(index.IndexName).OnTable(index.TableName).Do();
{
//if this is a unique constraint we need to drop the constraint, else drop the index
//to figure this out, the index must be tagged as unique and it must exist in the tableConstraints
if (index.IsUnique && uniqueConstraintNames.InvariantContains(index.IndexName))
Delete.UniqueConstraint(index.IndexName).FromTable(index.TableName).Do();
else
Delete.Index(index.IndexName).OnTable(index.TableName).Do();
}
}
}
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
{
class MarkdownEditorPreValueMigrator : DefaultPreValueMigrator //PreValueMigratorBase
{
public override bool CanMigrate(string editorAlias)
=> editorAlias == Constants.PropertyEditors.Aliases.MarkdownEditor;
protected override object GetPreValueValue(PreValueDto preValue)
{
if (preValue.Alias == "preview")
return preValue.Value == "1";
return base.GetPreValueValue(preValue);
}
}
}
@@ -20,7 +20,8 @@ public class PreValueMigratorComposer : ICoreComposer
.Append<DecimalPreValueMigrator>()
.Append<ListViewPreValueMigrator>()
.Append<DropDownFlexiblePreValueMigrator>()
.Append<ValueListPreValueMigrator>();
.Append<ValueListPreValueMigrator>()
.Append<MarkdownEditorPreValueMigrator>();
}
}
}
@@ -1,5 +1,6 @@
using System;
using System.Linq;
using Umbraco.Core.Exceptions;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
{
@@ -20,7 +21,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
case "Umbraco.NoEdit":
return Constants.PropertyEditors.Aliases.Label;
default:
throw new Exception("panic");
throw new PanicException($"The alias {editorAlias} is not supported");
}
}
}
@@ -15,21 +15,17 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
{
//special trick to add the column without constraints and return the sql to add them later
AddColumn<MacroDto>("macroType", out var sqls1);
//now we need to update the new column with some values because this column doesn't allow NULL values
Update.Table(Constants.DatabaseSchema.Tables.Macro).Set(new { macroType = (int)MacroTypes.Unknown}).AllRows().Do();
//now apply constraints (NOT NULL) to new table
foreach (var sql in sqls1) Execute.Sql(sql).Do();
//special trick to add the column without constraints and return the sql to add them later
AddColumn<MacroDto>("macroSource", out var sqls2);
//populate the new macroSource column with legacy data
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroXSLT, macroType = {(int)MacroTypes.Unknown} WHERE macroXSLT IS NOT NULL").Do();
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptAssembly, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptAssembly IS NOT NULL").Do();
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptType, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptType IS NOT NULL").Do();
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroPython, macroType = {(int)MacroTypes.PartialView} WHERE macroPython IS NOT NULL").Do();
//populate the new columns with legacy data
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = '', macroType = {(int)MacroTypes.Unknown}").Do();
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroXSLT, macroType = {(int)MacroTypes.Unknown} WHERE macroXSLT != '' AND macroXSLT IS NOT NULL").Do();
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptAssembly, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptAssembly != '' AND macroScriptAssembly IS NOT NULL").Do();
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptType, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptType != '' AND macroScriptType IS NOT NULL").Do();
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroPython, macroType = {(int)MacroTypes.PartialView} WHERE macroPython != '' AND macroPython IS NOT NULL").Do();
//now apply constraints (NOT NULL) to new table
foreach (var sql in sqls1) Execute.Sql(sql).Do();
foreach (var sql in sqls2) Execute.Sql(sql).Do();
//now remove these old columns
@@ -50,10 +50,10 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
var obj = JsonConvert.DeserializeObject<JObject>(value);
var allControls = obj.SelectTokens("$.sections..rows..areas..controls");
foreach (var control in allControls.SelectMany(c => c))
foreach (var control in allControls.SelectMany(c => c).OfType<JObject>())
{
var controlValue = control["value"];
if (controlValue.Type == JTokenType.String)
if (controlValue?.Type == JTokenType.String)
{
control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value<string>());
}
@@ -222,7 +222,13 @@ namespace Umbraco.Core.Models
return true;
}
public static void UnpublishCulture(this IContent content, string culture = "*")
/// <summary>
/// Returns false if the culture is already unpublished
/// </summary>
/// <param name="content"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static bool UnpublishCulture(this IContent content, string culture = "*")
{
culture = culture.NullOrWhiteSpaceAsNull();
@@ -230,16 +236,31 @@ namespace Umbraco.Core.Models
if (!content.ContentType.SupportsPropertyVariation(culture, "*", true))
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\".");
if (culture == "*") // all cultures
var keepProcessing = true;
if (culture == "*")
{
// all cultures
content.ClearPublishInfos();
else // one single culture
content.ClearPublishInfo(culture);
}
else
{
// one single culture
keepProcessing = content.ClearPublishInfo(culture);
}
// property.PublishValues only publishes what is valid, variation-wise
foreach (var property in content.Properties)
property.UnpublishValues(culture);
if (keepProcessing)
{
// property.PublishValues only publishes what is valid, variation-wise
foreach (var property in content.Properties)
property.UnpublishValues(culture);
content.PublishedState = PublishedState.Publishing;
content.PublishedState = PublishedState.Publishing;
}
return keepProcessing;
}
public static void ClearPublishInfos(this IContent content)
@@ -247,15 +268,24 @@ namespace Umbraco.Core.Models
content.PublishCultureInfos = null;
}
public static void ClearPublishInfo(this IContent content, string culture)
/// <summary>
/// Returns false if the culture is already unpublished
/// </summary>
/// <param name="content"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static bool ClearPublishInfo(this IContent content, string culture)
{
if (culture.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(culture));
content.PublishCultureInfos.Remove(culture);
// set the culture to be dirty - it's been modified
content.TouchCulture(culture);
var removed = content.PublishCultureInfos.Remove(culture);
if (removed)
{
// set the culture to be dirty - it's been modified
content.TouchCulture(culture);
}
return removed;
}
/// <summary>
+1 -1
View File
@@ -101,7 +101,7 @@ namespace Umbraco.Core.Models
PropertyGroupCollection PropertyGroups { get; set; }
/// <summary>
/// Gets all local property types belonging to a group, across all local property groups.
/// Gets all local property types all local property groups or ungrouped.
/// </summary>
IEnumerable<PropertyType> PropertyTypes { get; }
@@ -75,7 +75,7 @@ namespace Umbraco.Core.Models.PublishedContent
return type;
var def = type.GetGenericTypeDefinition();
if (def == null)
throw new InvalidOperationException("panic");
throw new PanicException($"The type {type} has not generic type definition");
var args = type.GetGenericArguments().Select(x => Map(x, modelTypes, true)).ToArray();
return def.MakeGenericType(args);
@@ -114,7 +114,7 @@ namespace Umbraco.Core.Models.PublishedContent
return type.FullName;
var def = type.GetGenericTypeDefinition();
if (def == null)
throw new InvalidOperationException("panic");
throw new PanicException($"The type {type} has not generic type definition");
var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray();
var defFullName = def.FullName.Substring(0, def.FullName.IndexOf('`'));
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml.Linq;
using System.Xml.XPath;
@@ -575,12 +574,11 @@ namespace Umbraco.Core.Packaging
contentType.Thumbnail = infoElement.Element("Thumbnail").Value;
contentType.Description = infoElement.Element("Description").Value;
//NOTE AllowAtRoot is a new property in the package xml so we need to verify it exists before using it.
//NOTE AllowAtRoot, IsListView, IsElement and Variations are new properties in the package xml so we need to verify it exists before using it.
var allowAtRoot = infoElement.Element("AllowAtRoot");
if (allowAtRoot != null)
contentType.AllowedAsRoot = allowAtRoot.Value.InvariantEquals("true");
//NOTE IsListView is a new property in the package xml so we need to verify it exists before using it.
var isListView = infoElement.Element("IsListView");
if (isListView != null)
contentType.IsContainer = isListView.Value.InvariantEquals("true");
@@ -589,6 +587,10 @@ namespace Umbraco.Core.Packaging
if (isElement != null)
contentType.IsElement = isElement.Value.InvariantEquals("true");
var variationsElement = infoElement.Element("Variations");
if (variationsElement != null)
contentType.Variations = (ContentVariation)Enum.Parse(typeof(ContentVariation), variationsElement.Value);
//Name of the master corresponds to the parent and we need to ensure that the Parent Id is set
var masterElement = infoElement.Element("Master");
if (masterElement != null)
@@ -614,7 +616,7 @@ namespace Umbraco.Core.Packaging
var compositionContentType = importedContentTypes.ContainsKey(compositionAlias)
? importedContentTypes[compositionAlias]
: _contentTypeService.Get(compositionAlias);
var added = contentType.AddContentType(compositionContentType);
contentType.AddContentType(compositionContentType);
}
}
}
@@ -748,9 +750,14 @@ namespace Umbraco.Core.Packaging
{
Name = property.Element("Name").Value,
Description = (string)property.Element("Description"),
Mandatory = property.Element("Mandatory") != null ? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true") : false,
Mandatory = property.Element("Mandatory") != null
? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true")
: false,
ValidationRegExp = (string)property.Element("Validation"),
SortOrder = sortOrder
SortOrder = sortOrder,
Variations = property.Element("Variations") != null
? (ContentVariation)Enum.Parse(typeof(ContentVariation), property.Element("Variations").Value)
: ContentVariation.Nothing
};
var tab = (string)property.Element("Tab");
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Factories
public static IEnumerable<Property> BuildEntities(PropertyType[] propertyTypes, IReadOnlyCollection<PropertyDataDto> dtos, int publishedVersionId, ILanguageRepository languageRepository)
{
var properties = new List<Property>();
var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable<PropertyDataDto>) x);
var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable<PropertyDataDto>)x);
foreach (var propertyType in propertyTypes)
{
@@ -104,10 +104,14 @@ namespace Umbraco.Core.Persistence.Factories
/// <param name="properties">The properties to map</param>
/// <param name="languageRepository"></param>
/// <param name="edited">out parameter indicating that one or more properties have been edited</param>
/// <param name="editedCultures">out parameter containing a collection of edited cultures when the contentVariation varies by culture</param>
/// <param name="editedCultures">
/// Out parameter containing a collection of edited cultures when the contentVariation varies by culture.
/// The value of this will be used to populate the edited cultures in the umbracoDocumentCultureVariation table.
/// </param>
/// <returns></returns>
public static IEnumerable<PropertyDataDto> BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable<Property> properties,
ILanguageRepository languageRepository, out bool edited, out HashSet<string> editedCultures)
ILanguageRepository languageRepository, out bool edited,
out HashSet<string> editedCultures)
{
var propertyDataDtos = new List<PropertyDataDto>();
edited = false;
@@ -130,6 +134,9 @@ namespace Umbraco.Core.Persistence.Factories
// publishing = deal with edit and published values
foreach (var propertyValue in property.Values)
{
var isInvariantValue = propertyValue.Culture == null;
var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null;
// deal with published value
if (propertyValue.PublishedValue != null && publishedVersionId > 0)
propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue));
@@ -138,26 +145,36 @@ namespace Umbraco.Core.Persistence.Factories
if (propertyValue.EditedValue != null)
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
// property.Values will contain ALL of it's values, both variant and invariant which will be populated if the
// administrator has previously changed the property type to be variant vs invariant.
// We need to check for this scenario here because otherwise the editedCultures and edited flags
// will end up incorrectly set in the umbracoDocumentCultureVariation table so here we need to
// only process edited cultures based on the current value type and how the property varies.
// The above logic will still persist the currently saved property value for each culture in case the admin
// decides to swap the property's variance again, in which case the edited flag will be recalculated.
if (property.PropertyType.VariesByCulture() && isInvariantValue || !property.PropertyType.VariesByCulture() && isCultureValue)
continue;
// use explicit equals here, else object comparison fails at comparing eg strings
var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue);
edited |= !sameValues;
if (entityVariesByCulture // cultures can be edited, ie CultureNeutral is supported
&& propertyValue.Culture != null && propertyValue.Segment == null // and value is CultureNeutral
&& !sameValues) // and edited and published are different
if (entityVariesByCulture && !sameValues)
{
editedCultures.Add(propertyValue.Culture); // report culture as edited
}
if (isCultureValue)
{
editedCultures.Add(propertyValue.Culture); // report culture as edited
}
else if (isInvariantValue)
{
// flag culture as edited if it contains an edited invariant property
if (defaultCulture == null)
defaultCulture = languageRepository.GetDefaultIsoCode();
// flag culture as edited if it contains an edited invariant property
if (propertyValue.Culture == null //invariant property
&& !sameValues // and edited and published are different
&& entityVariesByCulture) //only when the entity is variant
{
if (defaultCulture == null)
defaultCulture = languageRepository.GetDefaultIsoCode();
editedCultures.Add(defaultCulture);
editedCultures.Add(defaultCulture);
}
}
}
}
@@ -167,7 +184,7 @@ namespace Umbraco.Core.Persistence.Factories
{
// not publishing = only deal with edit values
if (propertyValue.EditedValue != null)
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
}
edited = true;
}
@@ -174,7 +174,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
totalRecords = page.TotalItems;
var items = page.Items.Select(
dto => new AuditItem(dto.Id, Enum<AuditType>.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList();
dto => new AuditItem(dto.NodeId, Enum<AuditType>.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList();
// map the DateStamp
for (var i = 0; i < items.Count; i++)
@@ -512,31 +512,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
foreach (var a in allPropertyDataDtos)
a.PropertyTypeDto = indexedPropertyTypeDtos[a.PropertyTypeId];
// prefetch configuration for tag properties
var tagEditors = new Dictionary<string, TagConfiguration>();
foreach (var propertyTypeDto in indexedPropertyTypeDtos.Values)
{
var editorAlias = propertyTypeDto.DataTypeDto.EditorAlias;
var editorAttribute = PropertyEditors[editorAlias].GetTagAttribute();
if (editorAttribute == null) continue;
var tagConfigurationSource = propertyTypeDto.DataTypeDto.Configuration;
var tagConfiguration = string.IsNullOrWhiteSpace(tagConfigurationSource)
? new TagConfiguration()
: JsonConvert.DeserializeObject<TagConfiguration>(tagConfigurationSource);
if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = editorAttribute.Delimiter;
tagEditors[editorAlias] = tagConfiguration;
}
// now we have
// - the definitions
// - all property data dtos
// - tag editors
// - tag editors (Actually ... no we don't since i removed that code, but we don't need them anyways it seems)
// and we need to build the proper property collections
return GetPropertyCollections(temps, allPropertyDataDtos, tagEditors);
return GetPropertyCollections(temps, allPropertyDataDtos);
}
private IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<TempContent<T>> temps, IEnumerable<PropertyDataDto> allPropertyDataDtos, Dictionary<string, TagConfiguration> tagConfigurations)
private IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<TempContent<T>> temps, IEnumerable<PropertyDataDto> allPropertyDataDtos)
where T : class, IContentBase
{
var result = new Dictionary<int, PropertyCollection>();
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
@@ -90,7 +91,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
contentType = ContentTypeFactory.BuildContentTypeEntity(contentTypeDto);
else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MemberType)
contentType = ContentTypeFactory.BuildMemberTypeEntity(contentTypeDto);
else throw new Exception("panic");
else throw new PanicException($"The node object type {contentTypeDto.NodeDto.NodeObjectType} is not supported");
contentTypes.Add(contentType.Id, contentType);
// map allowed content types
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -18,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// </summary>
internal class ContentTypeRepository : ContentTypeRepositoryBase<IContentType>, IContentTypeRepository
{
public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
: base(scopeAccessor, cache, logger, commonRepository)
public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => ContentType.SupportsPublishingConst;
@@ -56,7 +57,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
if (ids.Any()) throw new Exception("panic");
if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType<IContentType>();
}
@@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -26,14 +27,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
internal abstract class ContentTypeRepositoryBase<TEntity> : NPocoRepositoryBase<int, TEntity>, IReadRepository<Guid, TEntity>
where TEntity : class, IContentTypeComposition
{
protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger)
{
CommonRepository = commonRepository;
LanguageRepository = languageRepository;
}
protected IContentTypeCommonRepository CommonRepository { get; }
protected ILanguageRepository LanguageRepository { get; }
protected abstract bool SupportsPublishing { get; }
public IEnumerable<MoveEventInfo<TEntity>> Move(TEntity moving, EntityContainer container)
@@ -98,6 +100,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected void PersistNewBaseContentType(IContentTypeComposition entity)
{
ValidateVariations(entity);
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
//Cannot add a duplicate content type
@@ -163,11 +167,11 @@ AND umbracoNode.nodeObjectType = @objectType",
foreach (var allowedContentType in entity.AllowedContentTypes)
{
Database.Insert(new ContentTypeAllowedContentTypeDto
{
Id = entity.Id,
AllowedId = allowedContentType.Id.Value,
SortOrder = allowedContentType.SortOrder
});
{
Id = entity.Id,
AllowedId = allowedContentType.Id.Value,
SortOrder = allowedContentType.SortOrder
});
}
@@ -214,6 +218,8 @@ AND umbracoNode.nodeObjectType = @objectType",
protected void PersistUpdatedBaseContentType(IContentTypeComposition entity)
{
ValidateVariations(entity);
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
// ensure the alias is not used already
@@ -370,7 +376,7 @@ AND umbracoNode.id <> @id",
foreach (var propertyGroup in entity.PropertyGroups)
{
// insert or update group
var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup,entity.Id);
var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup, entity.Id);
var groupId = propertyGroup.HasIdentity
? Database.Update(groupDto)
: Convert.ToInt32(Database.Insert(groupDto));
@@ -388,7 +394,7 @@ AND umbracoNode.id <> @id",
//check if the content type variation has been changed
var contentTypeVariationDirty = entity.IsPropertyDirty("Variations");
var oldContentTypeVariation = (ContentVariation) dtoPk.Variations;
var oldContentTypeVariation = (ContentVariation)dtoPk.Variations;
var newContentTypeVariation = entity.Variations;
var contentTypeVariationChanging = contentTypeVariationDirty && oldContentTypeVariation != newContentTypeVariation;
if (contentTypeVariationChanging)
@@ -449,7 +455,7 @@ AND umbracoNode.id <> @id",
// via composition, with their original variations (ie not filtered by this
// content type variations - we need this true value to make decisions.
foreach (var propertyType in ((ContentTypeCompositionBase) entity).RawComposedPropertyTypes)
foreach (var propertyType in ((ContentTypeCompositionBase)entity).RawComposedPropertyTypes)
{
if (propertyType.VariesBySegment() || newContentTypeVariation.VariesBySegment())
throw new NotSupportedException(); // TODO: support this
@@ -518,6 +524,25 @@ AND umbracoNode.id <> @id",
CommonRepository.ClearCache(); // always
}
/// <summary>
/// Ensures that no property types are flagged for a variance that is not supported by the content type itself
/// </summary>
/// <param name="entity"></param>
private void ValidateVariations(IContentTypeComposition entity)
{
//if the entity does not vary at all, then the property cannot have a variance value greater than it
if (entity.Variations == ContentVariation.Nothing)
{
foreach (var prop in entity.PropertyTypes)
{
if (prop.IsPropertyDirty(nameof(prop.Variations)) && prop.Variations > entity.Variations)
throw new InvalidOperationException($"The property {prop.Alias} cannot have variations of {prop.Variations} with the content type variations of {entity.Variations}");
}
}
}
private IEnumerable<IContentTypeComposition> GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable<IContentTypeComposition> all)
{
var impact = new List<IContentTypeComposition>();
@@ -525,12 +550,12 @@ AND umbracoNode.id <> @id",
var tree = new Dictionary<int, List<IContentTypeComposition>>();
foreach (var x in all)
foreach (var y in x.ContentTypeComposition)
{
if (!tree.TryGetValue(y.Id, out var list))
list = tree[y.Id] = new List<IContentTypeComposition>();
list.Add(x);
}
foreach (var y in x.ContentTypeComposition)
{
if (!tree.TryGetValue(y.Id, out var list))
list = tree[y.Id] = new List<IContentTypeComposition>();
list.Add(x);
}
var nset = new List<IContentTypeComposition>();
do
@@ -572,7 +597,7 @@ AND umbracoNode.id <> @id",
// new property type, ignore
if (!oldVariations.TryGetValue(propertyType.Id, out var oldVariationB))
continue;
var oldVariation = (ContentVariation) oldVariationB; // NPoco cannot fetch directly
var oldVariation = (ContentVariation)oldVariationB; // NPoco cannot fetch directly
// only those property types that *actually* changed
var newVariation = propertyType.Variations;
@@ -636,7 +661,7 @@ AND umbracoNode.id <> @id",
var impactedL = impacted.Select(x => x.Id).ToList();
//Group by the "To" variation so we can bulk update in the correct batches
foreach(var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
foreach (var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
{
var propertyTypeIds = grouping.Select(x => x.Key).ToList();
var toVariation = grouping.Key;
@@ -646,10 +671,12 @@ AND umbracoNode.id <> @id",
case ContentVariation.Culture:
CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
break;
case ContentVariation.Nothing:
CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
break;
case ContentVariation.CultureAndSegment:
case ContentVariation.Segment:
@@ -963,6 +990,205 @@ AND umbracoNode.id <> @id",
Database.Execute(sqlDelete);
}
}
/// <summary>
/// Re-normalizes the edited value in the umbracoDocumentCultureVariation and umbracoDocument table when variations are changed
/// </summary>
/// <param name="propertyTypeIds"></param>
/// <param name="contentTypeIds"></param>
/// <remarks>
/// If this is not done, then in some cases the "edited" value for a particular culture for a document will remain true when it should be false
/// if the property was changed to invariant. In order to do this we need to recalculate this value based on the values stored for each
/// property, culture and current/published version.
/// </remarks>
private void RenormalizeDocumentEditedFlags(IReadOnlyCollection<int> propertyTypeIds, IReadOnlyCollection<int> contentTypeIds = null)
{
var defaultLang = LanguageRepository.GetDefaultId();
//This will build up a query to get the property values of both the current and the published version so that we can check
//based on the current variance of each item to see if it's 'edited' value should be true/false.
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
if (whereInArgsCount > 2000)
throw new NotSupportedException("Too many property/content types.");
var propertySql = Sql()
.Select<PropertyDataDto>()
.AndSelect<ContentVersionDto>(x => x.NodeId, x => x.Current)
.AndSelect<DocumentVersionDto>(x => x.Published)
.AndSelect<PropertyTypeDto>(x => x.Variations)
.From<PropertyDataDto>()
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, PropertyDataDto>((left, right) => left.Id == right.VersionId)
.InnerJoin<PropertyTypeDto>().On<PropertyTypeDto, PropertyDataDto>((left, right) => left.Id == right.PropertyTypeId);
if (contentTypeIds != null)
{
propertySql.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>((c, cversion) => c.NodeId == cversion.NodeId);
}
propertySql.LeftJoin<DocumentVersionDto>().On<DocumentVersionDto, ContentVersionDto>((docversion, cversion) => cversion.Id == docversion.Id)
.Where<DocumentVersionDto, ContentVersionDto>((docversion, cversion) => cversion.Current || docversion.Published)
.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
if (contentTypeIds != null)
{
propertySql.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIds);
}
propertySql
.OrderBy<ContentVersionDto>(x => x.NodeId)
.OrderBy<PropertyDataDto>(x => x.PropertyTypeId, x => x.LanguageId, x => x.VersionId);
//keep track of this node/lang to mark or unmark a culture as edited
var editedLanguageVersions = new Dictionary<(int nodeId, int? langId), bool>();
//keep track of which node to mark or unmark as edited
var editedDocument = new Dictionary<int, bool>();
var nodeId = -1;
var propertyTypeId = -1;
PropertyValueVersionDto pubRow = null;
//This is a reader (Query), we are not fetching this all into memory so we cannot make any changes during this iteration, we are just collecting data.
//Published data will always come before Current data based on the version id sort.
//There will only be one published row (max) and one current row per property.
foreach (var row in Database.Query<PropertyValueVersionDto>(propertySql))
{
//make sure to reset on each node/property change
if (nodeId != row.NodeId || propertyTypeId != row.PropertyTypeId)
{
nodeId = row.NodeId;
propertyTypeId = row.PropertyTypeId;
pubRow = null;
}
if (row.Published)
pubRow = row;
if (row.Current)
{
var propVariations = (ContentVariation)row.Variations;
//if this prop doesn't vary but the row has a lang assigned or vice versa, flag this as not edited
if (!propVariations.VariesByCulture() && row.LanguageId.HasValue
|| propVariations.VariesByCulture() && !row.LanguageId.HasValue)
{
//Flag this as not edited for this node/lang if the key doesn't exist
if (!editedLanguageVersions.TryGetValue((row.NodeId, row.LanguageId), out _))
editedLanguageVersions.Add((row.NodeId, row.LanguageId), false);
//mark as false if the item doesn't exist, else coerce to true
editedDocument[row.NodeId] = editedDocument.TryGetValue(row.NodeId, out var edited) ? (edited |= false) : false;
}
else if (pubRow == null)
{
//this would mean that that this property is 'edited' since there is no published version
editedLanguageVersions[(row.NodeId, row.LanguageId)] = true;
editedDocument[row.NodeId] = true;
}
//compare the property values, if they differ from versions then flag the current version as edited
else if (IsPropertyValueChanged(pubRow, row))
{
//Here we would check if the property is invariant, in which case the edited language should be indicated by the default lang
editedLanguageVersions[(row.NodeId, !propVariations.VariesByCulture() ? defaultLang : row.LanguageId)] = true;
editedDocument[row.NodeId] = true;
}
//reset
pubRow = null;
}
}
//lookup all matching rows in umbracoDocumentCultureVariation
var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(2000)
.SelectMany(_ => Database.Fetch<DocumentCultureVariationDto>(
Sql().Select<DocumentCultureVariationDto>().From<DocumentCultureVariationDto>()
.WhereIn<DocumentCultureVariationDto>(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList())
.WhereIn<DocumentCultureVariationDto>(x => x.NodeId, editedLanguageVersions.Keys.Select(x => x.nodeId))))
//convert to dictionary with the same key type
.ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x);
var toUpdate = new List<DocumentCultureVariationDto>();
foreach (var ev in editedLanguageVersions)
{
if (docCultureVariationsToUpdate.TryGetValue(ev.Key, out var docVariations))
{
//check if it needs updating
if (docVariations.Edited != ev.Value)
{
docVariations.Edited = ev.Value;
toUpdate.Add(docVariations);
}
}
else if (ev.Key.langId.HasValue)
{
//This should never happen! If a property culture is flagged as edited then the culture must exist at the document level
throw new PanicException($"The existing DocumentCultureVariationDto was not found for node {ev.Key.nodeId} and language {ev.Key.langId}");
}
}
//Now bulk update the table DocumentCultureVariationDto, once for edited = true, another for edited = false
foreach (var editValue in toUpdate.GroupBy(x => x.Edited))
{
Database.Execute(Sql().Update<DocumentCultureVariationDto>(u => u.Set(x => x.Edited, editValue.Key))
.WhereIn<DocumentCultureVariationDto>(x => x.Id, editValue.Select(x => x.Id)));
}
//Now bulk update the umbracoDocument table
foreach (var editValue in editedDocument.GroupBy(x => x.Value))
{
Database.Execute(Sql().Update<DocumentDto>(u => u.Set(x => x.Edited, editValue.Key))
.WhereIn<DocumentDto>(x => x.NodeId, editValue.Select(x => x.Key)));
}
}
private static bool IsPropertyValueChanged(PropertyValueVersionDto pubRow, PropertyValueVersionDto row)
{
return !pubRow.TextValue.IsNullOrWhiteSpace() && pubRow.TextValue != row.TextValue
|| !pubRow.VarcharValue.IsNullOrWhiteSpace() && pubRow.VarcharValue != row.VarcharValue
|| pubRow.DateValue.HasValue && pubRow.DateValue != row.DateValue
|| pubRow.DecimalValue.HasValue && pubRow.DecimalValue != row.DecimalValue
|| pubRow.IntValue.HasValue && pubRow.IntValue != row.IntValue;
}
private class NameCompareDto
{
public int NodeId { get; set; }
public int CurrentVersion { get; set; }
public int LanguageId { get; set; }
public string CurrentName { get; set; }
public string PublishedName { get; set; }
public int? PublishedVersion { get; set; }
public int Id { get; set; } // the Id of the DocumentCultureVariationDto
public bool Edited { get; set; }
}
private class PropertyValueVersionDto
{
public int VersionId { get; set; }
public int PropertyTypeId { get; set; }
public int? LanguageId { get; set; }
public string Segment { get; set; }
public int? IntValue { get; set; }
private decimal? _decimalValue;
[Column("decimalValue")]
public decimal? DecimalValue
{
get => _decimalValue;
set => _decimalValue = value?.Normalize();
}
public DateTime? DateValue { get; set; }
public string VarcharValue { get; set; }
public string TextValue { get; set; }
public int NodeId { get; set; }
public bool Current { get; set; }
public bool Published { get; set; }
public byte Variations { get; set; }
}
private void DeletePropertyType(int contentTypeId, int propertyTypeId)
@@ -386,7 +386,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
// insert document variations
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
}
// refresh content
@@ -511,7 +511,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id
documentVersionDto.Published = false; // non-published version
Database.Insert(documentVersionDto);
Database.Insert(documentVersionDto);
}
// replace the property data (rather than updating)
@@ -571,7 +571,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
// insert document variations
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
}
// refresh content
@@ -1297,25 +1297,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
};
}
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, bool publishing, HashSet<string> editedCultures)
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, HashSet<string> editedCultures)
{
var allCultures = content.AvailableCultures.Union(content.PublishedCultures); // union = distinct
foreach (var culture in allCultures)
yield return new DocumentCultureVariationDto
{
var dto = new DocumentCultureVariationDto
{
NodeId = content.Id,
LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."),
Culture = culture,
Name = content.GetCultureName(culture) ?? content.GetPublishName(culture),
// note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
Available = content.IsCultureAvailable(culture),
Published = content.IsCulturePublished(culture),
// note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
Edited = content.IsCultureAvailable(culture) &&
(!content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture)))
};
yield return dto;
}
}
private class ContentVariation
@@ -404,7 +404,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (isMedia)
{
sql
.InnerJoin<MediaVersionDto>().On<ContentVersionDto, MediaVersionDto>((left, right) => left.Id == right.Id);
.LeftJoin<MediaVersionDto>().On<ContentVersionDto, MediaVersionDto>((left, right) => left.Id == right.Id);
}
//Any LeftJoin statements need to come last
@@ -250,7 +250,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
lock (_codeIdMap)
{
if (_codeIdMap.TryGetValue(isoCode, out var id)) return id;
if (isoCode.Contains('-') && _codeIdMap.TryGetValue(isoCode.Split('-').First(), out var invariantId)) return invariantId;
}
if (throwOnNotFound)
throw new ArgumentException($"Code {isoCode} does not correspond to an existing language.", nameof(isoCode));
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -17,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// </summary>
internal class MediaTypeRepository : ContentTypeRepositoryBase<IMediaType>, IMediaTypeRepository
{
public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
: base(scopeAccessor, cache, logger, commonRepository)
public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => MediaType.SupportsPublishingConst;
@@ -50,7 +51,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
if (ids.Any()) throw new Exception("panic");
if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType<IMediaType>();
}
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -18,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// </summary>
internal class MemberTypeRepository : ContentTypeRepositoryBase<IMemberType>, IMemberTypeRepository
{
public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
: base(scopeAccessor, cache, logger, commonRepository)
public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => MemberType.SupportsPublishingConst;
@@ -57,7 +58,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
if (ids.Any()) throw new Exception("panic");
if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType<IMemberType>();
}
@@ -55,6 +55,7 @@ namespace Umbraco.Core.PropertyEditors
/// <summary>
/// Gets the type of the dynamic configuration provider.
/// </summary>
//TODO: This is not used and should be implemented in a nicer way, see https://github.com/umbraco/Umbraco-CMS/issues/6017#issuecomment-516253562
public Type TagsConfigurationProviderType { get; }
}
}
@@ -757,11 +757,11 @@ namespace Umbraco.Core.Services.Implement
{
var publishedState = content.PublishedState;
if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
throw new InvalidOperationException("Cannot save (un)publishing content, use the dedicated SavePublished method.");
throw new InvalidOperationException($"Cannot save (un)publishing content with name: {content.Name} - and state: {content.PublishedState}, use the dedicated SavePublished method.");
if (content.Name != null && content.Name.Length > 255)
{
throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
throw new InvalidOperationException($"Content with the name {content.Name} cannot be more than 255 characters in length.");
}
var evtMsgs = EventMessagesFactory.Get();
@@ -886,6 +886,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
var allLangs = _languageRepository.GetMany().ToList();
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
@@ -894,13 +896,13 @@ namespace Umbraco.Core.Services.Implement
// if culture is '*', then publish them all (including variants)
//this will create the correct culture impact even if culture is * or null
var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content);
var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
// publish the culture(s)
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
content.PublishCulture(impact);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
@@ -921,6 +923,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
var allLangs = _languageRepository.GetMany().ToList();
var evtMsgs = EventMessagesFactory.Get();
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
@@ -928,25 +932,23 @@ namespace Umbraco.Core.Services.Implement
var varies = content.ContentType.VariesByCulture();
if (cultures.Length == 0)
if (cultures.Length == 0 && !varies)
{
//no cultures specified and doesn't vary, so publish it, else nothing to publish
return !varies
? SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents)
: new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
return SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents);
}
if (cultures.Any(x => x == null || x == "*"))
throw new InvalidOperationException("Only valid cultures are allowed to be used in this method, wildcards or nulls are not allowed");
var impacts = cultures.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x)));
var impacts = cultures.Select(x => CultureImpact.Explicit(x, IsDefaultCulture(allLangs, x)));
// publish the culture(s)
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
foreach (var impact in impacts)
content.PublishCulture(impact);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
@@ -986,6 +988,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
var allLangs = _languageRepository.GetMany().ToList();
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
@@ -993,26 +997,39 @@ namespace Umbraco.Core.Services.Implement
// all cultures = unpublish whole
if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
{
// It's important to understand that when the document varies by culture but the "*" is used,
// we are just unpublishing the whole document but leaving all of the culture's as-is. This is expected
// because we don't want to actually unpublish every culture and then the document, we just want everything
// to be non-routable so that when it's re-published all variants were as they were.
content.PublishedState = PublishedState.Unpublishing;
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId);
scope.Complete();
return result;
}
else
{
// If the culture we want to unpublish was already unpublished, nothing to do.
// To check for that we need to lookup the persisted content item
var persisted = content.HasIdentity ? GetById(content.Id) : null;
// Unpublish the culture, this will change the document state to Publishing! ... which is expected because this will
// essentially be re-publishing the document with the requested culture removed.
// The call to CommitDocumentChangesInternal will perform all the checks like if this is a mandatory culture or the last culture being unpublished
// and will then unpublish the document accordingly.
// If the result of this is false it means there was no culture to unpublish (i.e. it was already unpublished or it did not exist)
var removed = content.UnpublishCulture(culture);
if (persisted != null && !persisted.IsCulturePublished(culture))
//save and publish any changes
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId);
scope.Complete();
// In one case the result will be PublishStatusType.FailedPublishNothingToPublish which means that no cultures
// were specified to be published which will be the case when removed is false. In that case
// we want to swap the result type to PublishResultType.SuccessUnpublishAlready (that was the expectation before).
if (result.Result == PublishResultType.FailedPublishNothingToPublish && !removed)
return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
// unpublish the culture
content.UnpublishCulture(culture);
return result;
}
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId);
scope.Complete();
return result;
}
}
/// <summary>
@@ -1047,15 +1064,35 @@ namespace Umbraco.Core.Services.Implement
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
var allLangs = _languageRepository.GetMany().ToList();
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
}
/// <summary>
/// Handles a lot of business logic cases for how the document should be persisted
/// </summary>
/// <param name="scope"></param>
/// <param name="content"></param>
/// <param name="saveEventArgs"></param>
/// <param name="userId"></param>
/// <param name="raiseEvents"></param>
/// <param name="branchOne"></param>
/// <param name="branchRoot"></param>
/// <returns></returns>
/// <remarks>
/// <para>
/// Business logic cases such: as unpublishing a mandatory culture, or unpublishing the last culture, checking for pending scheduled publishing, etc... is dealt with in this method.
/// There is quite a lot of cases to take into account along with logic that needs to deal with scheduled saving/publishing, branch saving/publishing, etc...
/// </para>
/// </remarks>
private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content,
ContentSavingEventArgs saveEventArgs,
int userId = Constants.Security.SuperUserId, bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
ContentSavingEventArgs saveEventArgs, IReadOnlyCollection<ILanguage> allLangs,
int userId = Constants.Security.SuperUserId,
bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
{
if (scope == null) throw new ArgumentNullException(nameof(scope));
if (content == null) throw new ArgumentNullException(nameof(content));
@@ -1070,8 +1107,8 @@ namespace Umbraco.Core.Services.Implement
if (content.PublishedState != PublishedState.Publishing && content.PublishedState != PublishedState.Unpublishing)
content.PublishedState = PublishedState.Publishing;
// state here is either Publishing or Unpublishing
// (even though, Publishing to unpublish a culture may end up unpublishing everything)
// State here is either Publishing or Unpublishing
// Publishing to unpublish a culture may end up unpublishing everything so these flags can be flipped later
var publishing = content.PublishedState == PublishedState.Publishing;
var unpublishing = content.PublishedState == PublishedState.Unpublishing;
@@ -1088,6 +1125,18 @@ namespace Umbraco.Core.Services.Implement
var changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch;
var previouslyPublished = content.HasIdentity && content.Published;
//inline method to persist the document with the documentRepository since this logic could be called a couple times below
void SaveDocument(IContent c)
{
// save, always
if (c.HasIdentity == false)
c.CreatorId = userId;
c.WriterId = userId;
// saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
_documentRepository.Save(c);
}
if (publishing)
{
//determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo
@@ -1097,11 +1146,25 @@ namespace Umbraco.Core.Services.Implement
: null;
// ensure that the document can be published, and publish handling events, business rules, etc
publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs);
publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs, allLangs);
if (publishResult.Success)
{
// note: StrategyPublish flips the PublishedState to Publishing!
publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, evtMsgs);
//check if a culture has been unpublished and if there are no cultures left, and then unpublish document as a whole
if (publishResult.Result == PublishResultType.SuccessUnpublishCulture && content.PublishCultureInfos.Count == 0)
{
// This is a special case! We are unpublishing the last culture and to persist that we need to re-publish without any cultures
// so the state needs to remain Publishing to do that. However, we then also need to unpublish the document and to do that
// the state needs to be Unpublishing and it cannot be both. This state is used within the documentRepository to know how to
// persist certain things. So before proceeding below, we need to save the Publishing state to publish no cultures, then we can
// mark the document for Unpublishing.
SaveDocument(content);
//set the flag to unpublish and continue
unpublishing = content.Published; // if not published yet, nothing to do
}
}
else
{
@@ -1162,13 +1225,8 @@ namespace Umbraco.Core.Services.Implement
}
}
// save, always
if (content.HasIdentity == false)
content.CreatorId = userId;
content.WriterId = userId;
// saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
_documentRepository.Save(content);
//Persist the document
SaveDocument(content);
// raise the Saved event, always
if (raiseEvents)
@@ -1186,17 +1244,34 @@ namespace Umbraco.Core.Services.Implement
if (culturesUnpublishing != null)
{
//If we are here, it means we tried unpublishing a culture but it was mandatory so now everything is unpublished
var langs = string.Join(", ", _languageRepository.GetMany()
// This will mean that that we unpublished a mandatory culture or we unpublished the last culture.
var langs = string.Join(", ", allLangs
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
//log that the whole content item has been unpublished due to mandatory culture unpublished
Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)");
}
else
Audit(AuditType.Unpublish, userId, content.Id);
if (publishResult == null)
throw new PanicException("publishResult == null - should not happen");
switch(publishResult.Result)
{
case PublishResultType.FailedPublishMandatoryCultureMissing:
//occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture)
//log that the whole content item has been unpublished due to mandatory culture unpublished
Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)");
return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture, evtMsgs, content);
case PublishResultType.SuccessUnpublishCulture:
//occurs when the last culture is unpublished
Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (last language unpublished)");
return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, evtMsgs, content);
}
}
Audit(AuditType.Unpublish, userId, content.Id);
return new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
}
@@ -1236,7 +1311,7 @@ namespace Umbraco.Core.Services.Implement
case PublishResultType.SuccessPublishCulture:
if (culturesPublishing != null)
{
var langs = string.Join(", ", _languageRepository.GetMany()
var langs = string.Join(", ", allLangs
.Where(x => culturesPublishing.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}", langs);
@@ -1245,7 +1320,7 @@ namespace Umbraco.Core.Services.Implement
case PublishResultType.SuccessUnpublishCulture:
if (culturesUnpublishing != null)
{
var langs = string.Join(", ", _languageRepository.GetMany()
var langs = string.Join(", ", allLangs
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
@@ -1259,14 +1334,14 @@ namespace Umbraco.Core.Services.Implement
// should not happen
if (branchOne && !branchRoot)
throw new Exception("panic");
throw new PanicException("branchOne && !branchRoot - should not happen");
//if publishing didn't happen or if it has failed, we still need to log which cultures were saved
if (!branchOne && (publishResult == null || !publishResult.Success))
{
if (culturesChanging != null)
{
var langs = string.Join(", ", _languageRepository.GetMany()
var langs = string.Join(", ", allLangs
.Where(x => culturesChanging.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.SaveVariant, userId, content.Id, $"Saved languages: {langs}", langs);
@@ -1297,6 +1372,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
var allLangs = _languageRepository.GetMany().ToList();
foreach (var d in _documentRepository.GetContentForRelease(date))
{
PublishResult result;
@@ -1325,7 +1402,7 @@ namespace Umbraco.Core.Services.Implement
//publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed
Property[] invalidProperties = null;
var impact = CultureImpact.Explicit(culture, _languageRepository.IsDefault(culture));
var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs, culture));
var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact);
if (invalidProperties != null && invalidProperties.Length > 0)
Logger.Warn<ContentService>("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}",
@@ -1340,7 +1417,7 @@ namespace Umbraco.Core.Services.Implement
else if (!publishing)
result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d);
else
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId);
if (result.Success == false)
@@ -1390,7 +1467,7 @@ namespace Umbraco.Core.Services.Implement
d.UnpublishCulture(c);
}
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId);
if (result.Success == false)
Logger.Error<ContentService>(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
yield return result;
@@ -1416,7 +1493,7 @@ namespace Umbraco.Core.Services.Implement
}
// utility 'PublishCultures' func used by SaveAndPublishBranch
private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet<string> culturesToPublish)
private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet<string> culturesToPublish, IReadOnlyCollection<ILanguage> allLangs)
{
//TODO: This does not support being able to return invalid property details to bubble up to the UI
@@ -1426,7 +1503,7 @@ namespace Umbraco.Core.Services.Implement
{
return culturesToPublish.All(culture =>
{
var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content);
var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
return content.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact);
});
}
@@ -1535,7 +1612,7 @@ namespace Umbraco.Core.Services.Implement
internal IEnumerable<PublishResult> SaveAndPublishBranch(IContent document, bool force,
Func<IContent, HashSet<string>> shouldPublish,
Func<IContent, HashSet<string>, bool> publishCultures,
Func<IContent, HashSet<string>, IReadOnlyCollection<ILanguage>, bool> publishCultures,
int userId = Constants.Security.SuperUserId)
{
if (shouldPublish == null) throw new ArgumentNullException(nameof(shouldPublish));
@@ -1549,6 +1626,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
var allLangs = _languageRepository.GetMany().ToList();
if (!document.HasIdentity)
throw new InvalidOperationException("Cannot not branch-publish a new document.");
@@ -1557,7 +1636,7 @@ namespace Umbraco.Core.Services.Implement
throw new InvalidOperationException("Cannot mix PublishCulture and SaveAndPublishBranch.");
// deal with the branch root - if it fails, abort
var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId);
var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId, allLangs);
if (result != null)
{
results.Add(result);
@@ -1588,7 +1667,7 @@ namespace Umbraco.Core.Services.Implement
}
// no need to check path here, parent has to be published here
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId);
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId, allLangs);
if (result != null)
{
results.Add(result);
@@ -1620,10 +1699,10 @@ namespace Umbraco.Core.Services.Implement
// publishValues: a function publishing values (using the appropriate PublishCulture calls)
private PublishResult SaveAndPublishBranchItem(IScope scope, IContent document,
Func<IContent, HashSet<string>> shouldPublish,
Func<IContent, HashSet<string>, bool> publishCultures,
Func<IContent, HashSet<string>, IReadOnlyCollection<ILanguage>, bool> publishCultures,
bool isRoot,
ICollection<IContent> publishedDocuments,
EventMessages evtMsgs, int userId)
EventMessages evtMsgs, int userId, IReadOnlyCollection<ILanguage> allLangs)
{
var culturesToPublish = shouldPublish(document);
if (culturesToPublish == null) // null = do not include
@@ -1636,13 +1715,13 @@ namespace Umbraco.Core.Services.Implement
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document);
// publish & check if values are valid
if (!publishCultures(document, culturesToPublish))
if (!publishCultures(document, culturesToPublish, allLangs))
{
//TODO: Based on this callback behavior there is no way to know which properties may have been invalid if this failed, see other results of FailedPublishContentInvalid
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document);
}
var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, userId, branchOne: true, branchRoot: isRoot);
var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, allLangs, userId, branchOne: true, branchRoot: isRoot);
if (result.Success)
publishedDocuments.Add(document);
return result;
@@ -2343,6 +2422,9 @@ namespace Umbraco.Core.Services.Implement
_auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Document), message, parameters));
}
private bool IsDefaultCulture(IReadOnlyCollection<ILanguage> langs, string culture) => langs.Any(x => x.IsDefault && x.IsoCode.InvariantEquals(culture));
private bool IsMandatoryCulture(IReadOnlyCollection<ILanguage> langs, string culture) => langs.Any(x => x.IsMandatory && x.IsoCode.InvariantEquals(culture));
#endregion
#region Event Handlers
@@ -2497,7 +2579,9 @@ namespace Umbraco.Core.Services.Implement
/// <param name="culturesPublishing"></param>
/// <param name="savingEventArgs"></param>
/// <returns></returns>
private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList<string> culturesPublishing, IReadOnlyCollection<string> culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs)
private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList<string> culturesPublishing,
IReadOnlyCollection<string> culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs,
IReadOnlyCollection<ILanguage> allLangs)
{
// raise Publishing event
if (scope.Events.DispatchCancelable(Publishing, this, savingEventArgs.ToContentPublishingEventArgs()))
@@ -2510,7 +2594,7 @@ namespace Umbraco.Core.Services.Implement
var impactsToPublish = culturesPublishing == null
? new[] {CultureImpact.Invariant} //if it's null it's invariant
: culturesPublishing.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x))).ToArray();
: culturesPublishing.Select(x => CultureImpact.Explicit(x, allLangs.Any(lang => lang.IsoCode.InvariantEquals(x) && lang.IsMandatory))).ToArray();
// publish the culture(s)
if (!impactsToPublish.All(content.PublishCulture))
@@ -2531,11 +2615,17 @@ namespace Umbraco.Core.Services.Implement
if (culturesPublishing == null)
throw new InvalidOperationException("Internal error, variesByCulture but culturesPublishing is null.");
if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0) // no published cultures = cannot be published
if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0)
{
// no published cultures = cannot be published
// This will occur if for example, a culture that is already unpublished is sent to be unpublished again, or vice versa, in that case
// there will be nothing to publish/unpublish.
return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
}
// missing mandatory culture = cannot be published
var mandatoryCultures = _languageRepository.GetMany().Where(x => x.IsMandatory).Select(x => x.IsoCode);
var mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode);
var mandatoryMissing = mandatoryCultures.Any(x => !content.PublishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase));
if (mandatoryMissing)
return new PublishResult(PublishResultType.FailedPublishMandatoryCultureMissing, evtMsgs, content);
@@ -2676,6 +2766,7 @@ namespace Umbraco.Core.Services.Implement
{
var attempt = new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
//TODO: What is this check?? we just created this attempt and of course it is Success?!
if (attempt.Success == false)
return attempt;
@@ -437,7 +437,8 @@ namespace Umbraco.Core.Services.Implement
new XElement("Description", contentType.Description),
new XElement("AllowAtRoot", contentType.AllowedAsRoot.ToString()),
new XElement("IsListView", contentType.IsContainer.ToString()),
new XElement("IsElement", contentType.IsElement.ToString()));
new XElement("IsElement", contentType.IsElement.ToString()),
new XElement("Variations", contentType.Variations.ToString()));
var masterContentType = contentType.ContentTypeComposition.FirstOrDefault(x => x.Id == contentType.ParentId);
if(masterContentType != null)
@@ -487,7 +488,8 @@ namespace Umbraco.Core.Services.Implement
new XElement("SortOrder", propertyType.SortOrder),
new XElement("Mandatory", propertyType.Mandatory.ToString()),
propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null,
propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null);
propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null,
new XElement("Variations", propertyType.Variations.ToString()));
genericProperties.Add(genericProperty);
}
@@ -49,6 +49,11 @@
/// </summary>
SuccessUnpublishMandatoryCulture = 6,
/// <summary>
/// The specified document culture was unpublished, and was the last published culture in the document, therefore the document itself was unpublished.
/// </summary>
SuccessUnpublishLastCulture = 8,
#endregion
#region Success - Mixed
@@ -113,9 +118,9 @@
FailedPublishContentInvalid = FailedPublish | 8,
/// <summary>
/// The document could not be published because it has no publishing flags or values.
/// The document could not be published because it has no publishing flags or values or if its a variant document, no cultures were specified to be published.
/// </summary>
FailedPublishNothingToPublish = FailedPublish | 9, // TODO: in ContentService.StrategyCanPublish - weird
FailedPublishNothingToPublish = FailedPublish | 9,
/// <summary>
/// The document could not be published because some mandatory cultures are missing.
@@ -97,8 +97,7 @@ namespace Umbraco.Core.Sync
? ":" + request.ServerVariables["SERVER_PORT"]
: "";
var useSsl = globalSettings.UseHttps || port == "443";
var ssl = useSsl ? "s" : ""; // force, whatever the first request
var ssl = globalSettings.UseHttps ? "s" : ""; // force, whatever the first request
var url = "http" + ssl + "://" + request.ServerVariables["SERVER_NAME"] + port + IOHelper.ResolveUrl(SystemDirectories.Umbraco);
return url.TrimEnd('/');
+2
View File
@@ -204,6 +204,7 @@
<Compile Include="CompositionExtensions_Essentials.cs" />
<Compile Include="CompositionExtensions_FileSystems.cs" />
<Compile Include="CompositionExtensions_Uniques.cs" />
<Compile Include="Exceptions\PanicException.cs" />
<Compile Include="FactoryExtensions.cs" />
<Compile Include="Composing\RegisterFactory.cs" />
<Compile Include="Composing\Current.cs" />
@@ -234,6 +235,7 @@
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\IPreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\ListViewPreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\MediaPickerPreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\MarkdownEditorPreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\NestedContentPreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueMigratorCollection.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueMigratorCollectionBuilder.cs" />
@@ -54,7 +54,7 @@ namespace Umbraco.Examine
{"updateDate", new object[] {c.UpdateDate}}, //Always add invariant updateDate
{"nodeName", (PublishedValuesOnly //Always add invariant nodeName
? c.PublishName?.Yield()
: c?.Name.Yield()) ?? Enumerable.Empty<string>()},
: c.Name?.Yield()) ?? Enumerable.Empty<string>()},
{"urlName", urlValue?.Yield() ?? Enumerable.Empty<string>()}, //Always add invariant urlName
{"path", c.Path?.Yield() ?? Enumerable.Empty<string>()},
{"nodeType", c.ContentType.Id.ToString().Yield() ?? Enumerable.Empty<string>()},
+24
View File
@@ -12,6 +12,7 @@ using Lucene.Net.Store;
using Umbraco.Core;
using Version = Lucene.Net.Util.Version;
using Umbraco.Core.Logging;
using System.Threading;
namespace Umbraco.Examine
{
@@ -28,6 +29,29 @@ namespace Umbraco.Examine
/// </remarks>
internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^([_\\w]+)_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled);
private static bool _isConfigured = false;
private static object _configuredInit = null;
private static object _isConfiguredLocker = new object();
/// <summary>
/// Called on startup to configure each index.
/// </summary>
/// <remarks>
/// Configures and unlocks all Lucene based indexes registered with the <see cref="IExamineManager"/>.
/// </remarks>
internal static void ConfigureIndexes(this IExamineManager examineManager, IMainDom mainDom, ILogger logger)
{
LazyInitializer.EnsureInitialized(
ref _configuredInit,
ref _isConfigured,
ref _isConfiguredLocker,
() =>
{
examineManager.ConfigureLuceneIndexes(logger, !mainDom.IsMainDom);
return null;
});
}
//TODO: We need a public method here to just match a field name against CultureIsoCodeFieldNameMatchExpression
/// <summary>
+2 -1
View File
@@ -5,7 +5,8 @@ using System.Threading.Tasks;
using Examine;
namespace Umbraco.Examine
{
{
/// <summary>
/// Utility to rebuild all indexes ensuring minimal data queries
/// </summary>
@@ -0,0 +1,83 @@
using System.Collections.Generic;
using Examine.LuceneEngine.Providers;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Lucene.Net.Store;
using Umbraco.Core.IO;
using System.Linq;
namespace Umbraco.Examine
{
public class LuceneIndexDiagnostics : IIndexDiagnostics
{
public LuceneIndexDiagnostics(LuceneIndex index, ILogger logger)
{
Index = index;
Logger = logger;
}
public LuceneIndex Index { get; }
public ILogger Logger { get; }
public int DocumentCount
{
get
{
try
{
return Index.GetIndexDocumentCount();
}
catch (AlreadyClosedException)
{
Logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexDocumentCount, the writer is already closed");
return 0;
}
}
}
public int FieldCount
{
get
{
try
{
return Index.GetIndexFieldCount();
}
catch (AlreadyClosedException)
{
Logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexFieldCount, the writer is already closed");
return 0;
}
}
}
public Attempt<string> IsHealthy()
{
var isHealthy = Index.IsHealthy(out var indexError);
return isHealthy ? Attempt<string>.Succeed() : Attempt.Fail(indexError.Message);
}
public virtual IReadOnlyDictionary<string, object> Metadata
{
get
{
var luceneDir = Index.GetLuceneDirectory();
var d = new Dictionary<string, object>
{
[nameof(UmbracoExamineIndex.CommitCount)] = Index.CommitCount,
[nameof(UmbracoExamineIndex.DefaultAnalyzer)] = Index.DefaultAnalyzer.GetType().Name,
["LuceneDirectory"] = luceneDir.GetType().Name
};
if (luceneDir is FSDirectory fsDir)
{
d[nameof(UmbracoExamineIndex.LuceneIndexFolder)] = fsDir.Directory.ToString().ToLowerInvariant().TrimStart(IOHelper.MapPath(SystemDirectories.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/');
}
return d;
}
}
}
}
@@ -72,6 +72,7 @@
<Compile Include="IPublishedContentValueSetBuilder.cs" />
<Compile Include="IUmbracoIndex.cs" />
<Compile Include="IValueSetBuilder.cs" />
<Compile Include="LuceneIndexDiagnostics.cs" />
<Compile Include="MediaIndexPopulator.cs" />
<Compile Include="MediaValueSetBuilder.cs" />
<Compile Include="MemberIndexPopulator.cs" />
@@ -7,73 +7,24 @@ using Umbraco.Core.Logging;
namespace Umbraco.Examine
{
public class UmbracoExamineIndexDiagnostics : IIndexDiagnostics
public class UmbracoExamineIndexDiagnostics : LuceneIndexDiagnostics
{
private readonly UmbracoExamineIndex _index;
private readonly ILogger _logger;
public UmbracoExamineIndexDiagnostics(UmbracoExamineIndex index, ILogger logger)
: base(index, logger)
{
_index = index;
_logger = logger;
}
public int DocumentCount
public override IReadOnlyDictionary<string, object> Metadata
{
get
{
try
{
return _index.GetIndexDocumentCount();
}
catch (AlreadyClosedException)
{
_logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexDocumentCount, the writer is already closed");
return 0;
}
}
}
var d = base.Metadata.ToDictionary(x => x.Key, x => x.Value);
public int FieldCount
{
get
{
try
{
return _index.GetIndexFieldCount();
}
catch (AlreadyClosedException)
{
_logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexFieldCount, the writer is already closed");
return 0;
}
}
}
public Attempt<string> IsHealthy()
{
var isHealthy = _index.IsHealthy(out var indexError);
return isHealthy ? Attempt<string>.Succeed() : Attempt.Fail(indexError.Message);
}
public virtual IReadOnlyDictionary<string, object> Metadata
{
get
{
var d = new Dictionary<string, object>
{
[nameof(UmbracoExamineIndex.CommitCount)] = _index.CommitCount,
[nameof(UmbracoExamineIndex.DefaultAnalyzer)] = _index.DefaultAnalyzer.GetType().Name,
["LuceneDirectory"] = _index.GetLuceneDirectory().GetType().Name,
[nameof(UmbracoExamineIndex.EnableDefaultEventHandler)] = _index.EnableDefaultEventHandler,
[nameof(UmbracoExamineIndex.LuceneIndexFolder)] =
_index.LuceneIndexFolder == null
? string.Empty
: _index.LuceneIndexFolder.ToString().ToLowerInvariant().TrimStart(IOHelper.MapPath(SystemDirectories.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/'),
[nameof(UmbracoExamineIndex.PublishedValuesOnly)] = _index.PublishedValuesOnly,
//There's too much info here
//[nameof(UmbracoExamineIndexer.FieldDefinitionCollection)] = _index.FieldDefinitionCollection,
};
d[nameof(UmbracoExamineIndex.EnableDefaultEventHandler)] = _index.EnableDefaultEventHandler;
d[nameof(UmbracoExamineIndex.PublishedValuesOnly)] = _index.PublishedValuesOnly;
if (_index.ValueSetValidator is ValueSetValidator vsv)
{
@@ -268,7 +268,7 @@ AnotherContentFinder
public void GetDataEditors()
{
var types = _typeLoader.GetDataEditors();
Assert.AreEqual(38, types.Count());
Assert.AreEqual(39, types.Count());
}
/// <summary>
@@ -29,10 +29,11 @@ namespace Umbraco.Tests.Persistence.Repositories
private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository)
{
var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(scopeAccessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
@@ -40,9 +41,10 @@ namespace Umbraco.Tests.Persistence.Repositories
private ContentTypeRepository CreateRepository(IScopeAccessor scopeAccessor)
{
var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
return contentTypeRepository;
}
@@ -50,7 +52,8 @@ namespace Umbraco.Tests.Persistence.Repositories
{
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
return contentTypeRepository;
}
@@ -67,8 +67,8 @@ namespace Umbraco.Tests.Persistence.Repositories
templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger);
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches);
contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository);
var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
@@ -23,8 +23,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository);
languageRepository = new LanguageRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository, languageRepository);
documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
return domainRepository;
@@ -79,39 +79,6 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
[Test]
public void Can_Perform_Get_By_Invariant_Code_On_LanguageRepository()
{
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var es = new CultureInfo("es");
var esSpecific = new CultureInfo("es-ES");
var language = (ILanguage)new Language(es.Name)
{
CultureName = es.DisplayName,
FallbackLanguageId = 1
};
repository.Save(language);
language = repository.GetByIsoCode(es.Name);
var languageSpecific = repository.GetByIsoCode(esSpecific.Name);
// Assert
Assert.That(language, Is.Not.Null);
Assert.That(language.HasIdentity, Is.True);
Assert.That(language.IsoCode, Is.EqualTo(es.Name));
Assert.That(languageSpecific, Is.Not.Null);
Assert.That(languageSpecific.HasIdentity, Is.True);
Assert.That(languageSpecific.Id, Is.EqualTo(language.Id));
Assert.That(language.IsoCode, Is.EqualTo(language.IsoCode));
}
}
[Test]
public void Get_When_Id_Doesnt_Exist_Returns_Null()
{
@@ -38,7 +38,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches);
mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository);
var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger);
var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
return repository;
@@ -23,7 +23,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var cacheHelper = AppCaches.Disabled;
var templateRepository = new TemplateRepository((IScopeAccessor)provider, cacheHelper, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches);
return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches, Logger);
return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
}
private EntityContainerRepository CreateContainerRepository(IScopeProvider provider)
@@ -31,7 +31,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var accessor = (IScopeAccessor) provider;
var templateRepository = Mock.Of<ITemplateRepository>();
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
memberGroupRepository = new MemberGroupRepository(accessor, AppCaches.Disabled, Logger);
var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger);
var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of<ILanguageRepository>());
@@ -24,7 +24,8 @@ namespace Umbraco.Tests.Persistence.Repositories
{
var templateRepository = Mock.Of<ITemplateRepository>();
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches);
return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of<ILogger>(), commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of<ILogger>());
return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of<ILogger>(), commonRepository, languageRepository);
}
[Test]
@@ -308,8 +308,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
@@ -956,8 +956,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
@@ -968,7 +968,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled);
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
return repository;
}
@@ -239,8 +239,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var tagRepository = new TagRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(ScopeProvider, templateRepository, AppCaches);
var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger);
var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage");
@@ -26,7 +26,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var accessor = (IScopeAccessor) provider;
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of<ILogger>(), commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of<ILogger>(), commonRepository, languageRepository);
var tagRepository = new TagRepository(accessor, AppCaches, Mock.Of<ILogger>());
var repository = new MediaRepository(accessor, AppCaches, Mock.Of<ILogger>(), mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
return repository;
@@ -44,8 +45,8 @@ namespace Umbraco.Tests.Persistence.Repositories
templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
@@ -40,7 +40,7 @@ namespace Umbraco.Tests.Routing
{
base.SetUp();
WebFinalComponent.CreateRoutes(
WebInitialComponent.CreateRoutes(
new TestUmbracoContextAccessor(),
TestObjects.GetGlobalSettings(),
new SurfaceControllerTypeCollection(Enumerable.Empty<Type>()),
@@ -166,8 +166,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
@@ -200,8 +200,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
@@ -232,8 +232,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor) provider, tRepository, AppCaches);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
@@ -267,8 +267,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
+207 -28
View File
@@ -717,21 +717,8 @@ namespace Umbraco.Tests.Services
[Test]
public void Can_Unpublish_Content_Variation()
{
// Arrange
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
var langUk = new Language("en-GB") { IsDefault = true };
var langFr = new Language("fr-FR");
ServiceContext.LocalizationService.Save(langFr);
ServiceContext.LocalizationService.Save(langUk);
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
IContent content = new Content("content", Constants.System.Root, contentType);
content.SetCultureName("content-fr", langFr.IsoCode);
content.SetCultureName("content-en", langUk.IsoCode);
content.PublishCulture(CultureImpact.Explicit(langFr.IsoCode, langFr.IsDefault));
content.PublishCulture(CultureImpact.Explicit(langUk.IsoCode, langUk.IsDefault));
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
@@ -761,6 +748,185 @@ namespace Umbraco.Tests.Services
}
[Test]
public void Can_Publish_Culture_After_Last_Culture_Unpublished()
{
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
Assert.AreEqual(PublishedState.Published, content.PublishedState);
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //first culture
Assert.IsTrue(unpublished.Success);
Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
content = ServiceContext.ContentService.GetById(content.Id);
unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); //last culture
Assert.IsTrue(unpublished.Success);
Assert.AreEqual(PublishResultType.SuccessUnpublishLastCulture, unpublished.Result);
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
content = ServiceContext.ContentService.GetById(content.Id);
published = ServiceContext.ContentService.SaveAndPublish(content, langUk.IsoCode);
Assert.AreEqual(PublishedState.Published, content.PublishedState);
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
content = ServiceContext.ContentService.GetById(content.Id); //reget
Assert.AreEqual(PublishedState.Published, content.PublishedState);
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
}
[Test]
public void Unpublish_All_Cultures_Has_Unpublished_State()
{
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.IsTrue(published.Success);
Assert.AreEqual(PublishedState.Published, content.PublishedState);
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.AreEqual(PublishedState.Published, content.PublishedState);
var unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); //first culture
Assert.IsTrue(unpublished.Success);
Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.AreEqual(PublishedState.Published, content.PublishedState); //still published
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //last culture
Assert.IsTrue(unpublished.Success);
Assert.AreEqual(PublishResultType.SuccessUnpublishLastCulture, unpublished.Result);
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //the last culture was unpublished so the document should also reflect this
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //just double checking
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
}
[Test]
public void Unpublishing_Mandatory_Language_Unpublishes_Document()
{
var langUk = new Language("en-GB") { IsDefault = true, IsMandatory = true };
var langFr = new Language("fr-FR");
ServiceContext.LocalizationService.Save(langFr);
ServiceContext.LocalizationService.Save(langUk);
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
IContent content = new Content("content", Constants.System.Root, contentType);
content.SetCultureName("content-fr", langFr.IsoCode);
content.SetCultureName("content-en", langUk.IsoCode);
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.IsTrue(published.Success);
Assert.AreEqual(PublishedState.Published, content.PublishedState);
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //unpublish mandatory lang
Assert.IsTrue(unpublished.Success);
Assert.AreEqual(PublishResultType.SuccessUnpublishMandatoryCulture, unpublished.Result);
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); //remains published
Assert.AreEqual(PublishedState.Unpublished, content.PublishedState);
}
[Test]
public void Unpublishing_Already_Unpublished_Culture()
{
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.IsTrue(published.Success);
Assert.AreEqual(PublishedState.Published, content.PublishedState);
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode);
Assert.IsTrue(unpublished.Success);
Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
content = ServiceContext.ContentService.GetById(content.Id);
//Change some data since Unpublish should always Save
content.SetCultureName("content-en-updated", langUk.IsoCode);
unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //unpublish again
Assert.IsTrue(unpublished.Success);
Assert.AreEqual(PublishResultType.SuccessUnpublishAlready, unpublished.Result);
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
content = ServiceContext.ContentService.GetById(content.Id);
//ensure that even though the culture was already unpublished that the data was still persisted
Assert.AreEqual("content-en-updated", content.GetCultureName(langUk.IsoCode));
}
[Test]
public void Publishing_No_Cultures_Still_Saves()
{
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.IsTrue(published.Success);
Assert.AreEqual(PublishedState.Published, content.PublishedState);
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
//Change some data since SaveAndPublish should always Save
content.SetCultureName("content-en-updated", langUk.IsoCode);
var saved = ServiceContext.ContentService.SaveAndPublish(content, new string [] { }); //save without cultures
Assert.AreEqual(PublishResultType.FailedPublishNothingToPublish, saved.Result);
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
//ensure that even though nothing was published that the data was still persisted
Assert.AreEqual("content-en-updated", content.GetCultureName(langUk.IsoCode));
}
[Test]
public void Pending_Invariant_Property_Changes_Affect_Default_Language_Edited_State()
{
@@ -811,17 +977,7 @@ namespace Umbraco.Tests.Services
[Test]
public void Can_Publish_Content_Variation_And_Detect_Changed_Cultures()
{
// Arrange
var langGB = new Language("en-GB") { IsDefault = true };
var langFr = new Language("fr-FR");
ServiceContext.LocalizationService.Save(langFr);
ServiceContext.LocalizationService.Save(langGB);
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
CreateEnglishAndFrenchDocumentType(out var langUk, out var langFr, out var contentType);
IContent content = new Content("content", Constants.System.Root, contentType);
content.SetCultureName("content-fr", langFr.IsoCode);
@@ -832,8 +988,8 @@ namespace Umbraco.Tests.Services
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
content.SetCultureName("content-en", langGB.IsoCode);
published = ServiceContext.ContentService.SaveAndPublish(content, langGB.IsoCode);
content.SetCultureName("content-en", langUk.IsoCode);
published = ServiceContext.ContentService.SaveAndPublish(content, langUk.IsoCode);
//audit log will only show that english was published
lastLog = ServiceContext.AuditService.GetLogs(content.Id).Last();
Assert.AreEqual($"Published languages: English (United Kingdom)", lastLog.Comment);
@@ -3008,10 +3164,33 @@ namespace Umbraco.Tests.Services
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
private void CreateEnglishAndFrenchDocumentType(out Language langUk, out Language langFr, out ContentType contentType)
{
langUk = new Language("en-GB") { IsDefault = true };
langFr = new Language("fr-FR");
ServiceContext.LocalizationService.Save(langFr);
ServiceContext.LocalizationService.Save(langUk);
contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
}
private IContent CreateEnglishAndFrenchDocument(out Language langUk, out Language langFr, out ContentType contentType)
{
CreateEnglishAndFrenchDocumentType(out langUk, out langFr, out contentType);
IContent content = new Content("content", Constants.System.Root, contentType);
content.SetCultureName("content-fr", langFr.IsoCode);
content.SetCultureName("content-en", langUk.IsoCode);
return content;
}
}
}
@@ -53,360 +53,7 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(contentType.IsElement);
}
[Test]
public void Change_Content_Type_Variation_Clears_Redirects()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Nothing
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
ServiceContext.ContentTypeService.Save(contentType2);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.Name = "Hello1";
ServiceContext.ContentService.Save(doc);
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
ServiceContext.ContentService.Save(doc2);
ServiceContext.RedirectUrlService.Register("hello/world", doc.Key);
ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key);
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
//change variation
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
}
[Test]
public void Change_Content_Type_From_Invariant_Variant()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Nothing
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.Name = "Hello1";
doc.SetValue("title", "hello world");
ServiceContext.ContentService.Save(doc);
Assert.AreEqual("Hello1", doc.Name);
Assert.AreEqual("hello world", doc.GetValue("title"));
//change the content type to be variant, we will also update the name here to detect the copy changes
doc.Name = "Hello2";
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello2", doc.GetCultureName("en-US"));
Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant
//change back property type to be invariant, we will also update the name here to detect the copy changes
doc.SetCultureName("Hello3", "en-US");
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello3", doc.Name);
Assert.AreEqual("hello world", doc.GetValue("title"));
}
[Test]
public void Change_Content_Type_From_Variant_Invariant()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Culture
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Hello1", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
Assert.AreEqual("Hello1", doc.GetCultureName("en-US"));
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
//change the content type to be invariant, we will also update the name here to detect the copy changes
doc.SetCultureName("Hello2", "en-US");
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello2", doc.Name);
Assert.AreEqual("hello world", doc.GetValue("title"));
//change back property type to be variant, we will also update the name here to detect the copy changes
doc.Name = "Hello3";
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
//at this stage all property types were switched to invariant so even though the variant value
//exists it will not be returned because the property type is invariant,
//so this check proves that null will be returned
Assert.IsNull(doc.GetValue("title", "en-US"));
//we can now switch the property type to be variant and the value can be returned again
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello3", doc.GetCultureName("en-US"));
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
}
[Test]
public void Change_Property_Type_From_Invariant_Variant()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Nothing
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.Name = "Home";
doc.SetValue("title", "hello world");
ServiceContext.ContentService.Save(doc);
Assert.AreEqual("hello world", doc.GetValue("title"));
//change the property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
//change back property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
}
[Test]
public void Change_Property_Type_From_Variant_Invariant()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Culture
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Home", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
//change the property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
//change back property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
}
[Test]
public void Change_Property_Type_From_Variant_Invariant_On_A_Composition()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Culture
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
//compose this from the other one
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
contentType2.Variations = ContentVariation.Culture;
contentType2.AddContentType(contentType);
ServiceContext.ContentTypeService.Save(contentType2);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Home", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
doc2.SetCultureName("Home", "en-US");
doc2.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc2);
//change the property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.AreEqual("hello world", doc2.GetValue("title"));
//change back property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
Assert.AreEqual("hello world", doc2.GetValue("title", "en-US"));
}
[Test]
public void Change_Content_Type_From_Variant_Invariant_On_A_Composition()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Culture
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
//compose this from the other one
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
contentType2.Variations = ContentVariation.Culture;
contentType2.AddContentType(contentType);
ServiceContext.ContentTypeService.Save(contentType2);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Home", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
doc2.SetCultureName("Home", "en-US");
doc2.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc2);
//change the content type to be invariant
contentType.Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.AreEqual("hello world", doc2.GetValue("title"));
//change back content type to be variant
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
//this will be null because the doc type was changed back to variant but it's property types don't get changed back
Assert.IsNull(doc.GetValue("title", "en-US"));
Assert.IsNull(doc2.GetValue("title", "en-US"));
}
[Test]
public void Deleting_Content_Type_With_Hierarchy_Of_Content_Items_Moves_Orphaned_Content_To_Recycle_Bin()
@@ -17,6 +17,7 @@ using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.PublishedCache.NuCache;
@@ -106,44 +107,346 @@ namespace Umbraco.Tests.Services
}
}
[Test]
public void Change_Content_Type_Variation_Clears_Redirects()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
ServiceContext.ContentTypeService.Save(contentType2);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.Name = "Hello1";
ServiceContext.ContentService.Save(doc);
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
ServiceContext.ContentService.Save(doc2);
ServiceContext.RedirectUrlService.Register("hello/world", doc.Key);
ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key);
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
//change variation
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
}
[Test]
public void Change_Content_Type_From_Invariant_Variant()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.Name = "Hello1";
doc.SetValue("title", "hello world");
ServiceContext.ContentService.Save(doc);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello1", doc.Name);
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.IsTrue(doc.Edited);
Assert.IsFalse (doc.IsCultureEdited("en-US"));
//change the content type to be variant, we will also update the name here to detect the copy changes
doc.Name = "Hello2";
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello2", doc.GetCultureName("en-US"));
Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant
Assert.IsTrue(doc.Edited);
Assert.IsTrue(doc.IsCultureEdited("en-US"));
//change back property type to be invariant, we will also update the name here to detect the copy changes
doc.SetCultureName("Hello3", "en-US");
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello3", doc.Name);
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.IsTrue(doc.Edited);
Assert.IsFalse(doc.IsCultureEdited("en-US"));
}
[Test]
public void Change_Content_Type_From_Variant_Invariant()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Hello1", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello1", doc.GetCultureName("en-US"));
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
Assert.IsTrue(doc.Edited);
Assert.IsTrue(doc.IsCultureEdited("en-US"));
//change the content type to be invariant, we will also update the name here to detect the copy changes
doc.SetCultureName("Hello2", "en-US");
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello2", doc.Name);
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.IsTrue(doc.Edited);
Assert.IsFalse(doc.IsCultureEdited("en-US"));
//change back property type to be variant, we will also update the name here to detect the copy changes
doc.Name = "Hello3";
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
//at this stage all property types were switched to invariant so even though the variant value
//exists it will not be returned because the property type is invariant,
//so this check proves that null will be returned
Assert.IsNull(doc.GetValue("title", "en-US"));
Assert.IsTrue(doc.Edited);
Assert.IsTrue(doc.IsCultureEdited("en-US")); // this is true because the name change is copied to the default language
//we can now switch the property type to be variant and the value can be returned again
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello3", doc.GetCultureName("en-US"));
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
Assert.IsTrue(doc.Edited);
Assert.IsTrue(doc.IsCultureEdited("en-US"));
}
[Test]
public void Change_Property_Type_From_To_Variant_On_Invariant_Content_Type()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//change the property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
//Cannot change a property type to be variant if the content type itself is not variant
Assert.Throws<InvalidOperationException>(() => ServiceContext.ContentTypeService.Save(contentType));
}
[Test]
public void Change_Property_Type_From_Invariant_Variant()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Home", "en-US");
doc.SetValue("title", "hello world");
ServiceContext.ContentService.Save(doc);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.IsTrue(doc.IsCultureEdited("en-US")); //invariant prop changes show up on default lang
Assert.IsTrue(doc.Edited);
//change the property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
Assert.IsTrue(doc.IsCultureEdited("en-US"));
Assert.IsTrue(doc.Edited);
//change back property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.IsTrue(doc.IsCultureEdited("en-US")); //invariant prop changes show up on default lang
Assert.IsTrue(doc.Edited);
}
[Test]
public void Change_Property_Type_From_Variant_Invariant()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Home", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
//change the property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
//change back property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
}
[Test]
public void Change_Property_Type_From_Variant_Invariant_On_A_Composition()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//compose this from the other one
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
contentType2.Variations = ContentVariation.Culture;
contentType2.AddContentType(contentType);
ServiceContext.ContentTypeService.Save(contentType2);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Home", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
doc2.SetCultureName("Home", "en-US");
doc2.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc2);
//change the property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.AreEqual("hello world", doc2.GetValue("title"));
//change back property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
Assert.AreEqual("hello world", doc2.GetValue("title", "en-US"));
}
[Test]
public void Change_Content_Type_From_Variant_Invariant_On_A_Composition()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//compose this from the other one
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
contentType2.Variations = ContentVariation.Culture;
contentType2.AddContentType(contentType);
ServiceContext.ContentTypeService.Save(contentType2);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Home", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
doc2.SetCultureName("Home", "en-US");
doc2.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc2);
//change the content type to be invariant
contentType.Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.AreEqual("hello world", doc2.GetValue("title"));
//change back content type to be variant
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
//this will be null because the doc type was changed back to variant but it's property types don't get changed back
Assert.IsNull(doc.GetValue("title", "en-US"));
Assert.IsNull(doc2.GetValue("title", "en-US"));
}
[Test]
public void Change_Variations_SimpleContentType_VariantToInvariantAndBack()
{
// one simple content type, variant, with both variant and invariant properties
// can change it to invariant and back
var languageEn = new Language("en") { IsDefault = true };
ServiceContext.LocalizationService.Save(languageEn);
var languageFr = new Language("fr");
ServiceContext.LocalizationService.Save(languageFr);
CreateFrenchAndEnglishLangs();
var contentType = new ContentType(-1)
{
Alias = "contentType",
Name = "contentType",
Variations = ContentVariation.Culture
};
var contentType = CreateContentType(ContentVariation.Culture);
var properties = new PropertyTypeCollection(true)
{
new PropertyType("value1", ValueStorageType.Ntext)
{
Alias = "value1",
DataTypeId = -88,
Variations = ContentVariation.Culture
},
new PropertyType("value2", ValueStorageType.Ntext)
{
Alias = "value2",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties = CreatePropertyCollection(
("value1", ContentVariation.Culture),
("value2", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
var document = (IContent) new Content("document", -1, contentType);
var document = (IContent)new Content("document", -1, contentType);
document.SetCultureName("doc1en", "en");
document.SetCultureName("doc1fr", "fr");
document.SetValue("value1", "v1en", "en");
@@ -226,28 +529,11 @@ namespace Umbraco.Tests.Services
var languageFr = new Language("fr");
ServiceContext.LocalizationService.Save(languageFr);
var contentType = new ContentType(-1)
{
Alias = "contentType",
Name = "contentType",
Variations = ContentVariation.Nothing
};
var contentType = CreateContentType(ContentVariation.Nothing);
var properties = new PropertyTypeCollection(true)
{
new PropertyType("value1", ValueStorageType.Ntext)
{
Alias = "value1",
DataTypeId = -88,
Variations = ContentVariation.Nothing
},
new PropertyType("value2", ValueStorageType.Ntext)
{
Alias = "value2",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties = CreatePropertyCollection(
("value1", ContentVariation.Nothing),
("value2", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -326,33 +612,13 @@ namespace Umbraco.Tests.Services
// one simple content type, variant, with both variant and invariant properties
// can change an invariant property to variant and back
var languageEn = new Language("en") { IsDefault = true };
ServiceContext.LocalizationService.Save(languageEn);
var languageFr = new Language("fr");
ServiceContext.LocalizationService.Save(languageFr);
CreateFrenchAndEnglishLangs();
var contentType = new ContentType(-1)
{
Alias = "contentType",
Name = "contentType",
Variations = ContentVariation.Culture
};
var contentType = CreateContentType(ContentVariation.Culture);
var properties = new PropertyTypeCollection(true)
{
new PropertyType("value1", ValueStorageType.Ntext)
{
Alias = "value1",
DataTypeId = -88,
Variations = ContentVariation.Culture
},
new PropertyType("value2", ValueStorageType.Ntext)
{
Alias = "value2",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties = CreatePropertyCollection(
("value1", ContentVariation.Culture),
("value2", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -429,6 +695,185 @@ namespace Umbraco.Tests.Services
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':");
}
[Test]
public void Change_Property_Variations_From_Variant_To_Invariant_And_Ensure_Edited_Values_Are_Renormalized()
{
// one simple content type, variant, with both variant and invariant properties
// can change an invariant property to variant and back
CreateFrenchAndEnglishLangs();
var contentType = CreateContentType(ContentVariation.Culture);
var properties = CreatePropertyCollection(("value1", ContentVariation.Culture));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
var document = (IContent)new Content("document", -1, contentType);
document.SetCultureName("doc1en", "en");
document.SetCultureName("doc1fr", "fr");
document.SetValue("value1", "v1en-init", "en");
document.SetValue("value1", "v1fr-init", "fr");
ServiceContext.ContentService.SaveAndPublish(document); //all values are published which means the document is not 'edited'
document = ServiceContext.ContentService.GetById(document.Id);
Assert.IsFalse(document.IsCultureEdited("en"));
Assert.IsFalse(document.IsCultureEdited("fr"));
Assert.IsFalse(document.Edited);
document.SetValue("value1", "v1en", "en"); //change the property culture value, so now this culture will be edited
document.SetValue("value1", "v1fr", "fr"); //change the property culture value, so now this culture will be edited
ServiceContext.ContentService.Save(document);
document = ServiceContext.ContentService.GetById(document.Id);
Assert.AreEqual("doc1en", document.Name);
Assert.AreEqual("doc1en", document.GetCultureName("en"));
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
Assert.AreEqual("v1en", document.GetValue("value1", "en"));
Assert.AreEqual("v1en-init", document.GetValue("value1", "en", published: true));
Assert.AreEqual("v1fr", document.GetValue("value1", "fr"));
Assert.AreEqual("v1fr-init", document.GetValue("value1", "fr", published: true));
Assert.IsTrue(document.IsCultureEdited("en")); //This will be true because the edited value isn't the same as the published value
Assert.IsTrue(document.IsCultureEdited("fr")); //This will be true because the edited value isn't the same as the published value
Assert.IsTrue(document.Edited);
// switch property type to Invariant
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag
document = ServiceContext.ContentService.GetById(document.Id);
Assert.IsTrue(document.IsCultureEdited("en")); //This will remain true because there is now a pending change for the invariant property data which is flagged under the default lang
Assert.IsFalse(document.IsCultureEdited("fr")); //This will be false because nothing has changed for this culture and the property no longer reflects variant changes
Assert.IsTrue(document.Edited);
//update the invariant value and publish
document.SetValue("value1", "v1inv");
ServiceContext.ContentService.SaveAndPublish(document);
document = ServiceContext.ContentService.GetById(document.Id);
Assert.AreEqual("doc1en", document.Name);
Assert.AreEqual("doc1en", document.GetCultureName("en"));
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
Assert.IsNull(document.GetValue("value1", "en")); //The values are there but the business logic returns null
Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null
Assert.IsNull(document.GetValue("value1", "en", published: true)); //The values are there but the business logic returns null
Assert.IsNull(document.GetValue("value1", "fr", published: true)); //The values are there but the business logic returns null
Assert.AreEqual("v1inv", document.GetValue("value1"));
Assert.AreEqual("v1inv", document.GetValue("value1", published: true));
Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published
Assert.IsFalse(document.IsCultureEdited("fr")); //This will be false because nothing has changed for this culture and the property no longer reflects variant changes
Assert.IsFalse(document.Edited);
// switch property back to Culture
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
document = ServiceContext.ContentService.GetById(document.Id);
Assert.AreEqual("v1inv", document.GetValue("value1", "en")); //The invariant property value gets copied over to the default language
Assert.AreEqual("v1inv", document.GetValue("value1", "en", published: true));
Assert.AreEqual("v1fr", document.GetValue("value1", "fr")); //values are still retained
Assert.AreEqual("v1fr-init", document.GetValue("value1", "fr", published: true)); //values are still retained
Assert.IsFalse(document.IsCultureEdited("en")); //The invariant published AND edited values are copied over to the default language
Assert.IsTrue(document.IsCultureEdited("fr")); //The previously existing french values are there and there is no published value
Assert.IsTrue(document.Edited); //Will be flagged edited again because the french culture had pending changes
// publish again
document.SetValue("value1", "v1en2", "en"); //update the value now that it's variant again
document.SetValue("value1", "v1fr2", "fr"); //update the value now that it's variant again
ServiceContext.ContentService.SaveAndPublish(document);
document = ServiceContext.ContentService.GetById(document.Id);
Assert.AreEqual("doc1en", document.Name);
Assert.AreEqual("doc1en", document.GetCultureName("en"));
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
Assert.AreEqual("v1en2", document.GetValue("value1", "en"));
Assert.AreEqual("v1fr2", document.GetValue("value1", "fr"));
Assert.IsNull(document.GetValue("value1")); //The value is there but the business logic returns null
Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, the variant property value has been published
Assert.IsFalse(document.IsCultureEdited("fr")); //This returns false, the variant property value has been published
Assert.IsFalse(document.Edited);
}
[Test]
public void Change_Property_Variations_From_Invariant_To_Variant_And_Ensure_Edited_Values_Are_Renormalized()
{
// one simple content type, variant, with both variant and invariant properties
// can change an invariant property to variant and back
CreateFrenchAndEnglishLangs();
var contentType = CreateContentType(ContentVariation.Culture);
var properties = CreatePropertyCollection(("value1", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
var document = (IContent)new Content("document", -1, contentType);
document.SetCultureName("doc1en", "en");
document.SetCultureName("doc1fr", "fr");
document.SetValue("value1", "v1en-init");
ServiceContext.ContentService.SaveAndPublish(document); //all values are published which means the document is not 'edited'
document = ServiceContext.ContentService.GetById(document.Id);
Assert.IsFalse(document.IsCultureEdited("en"));
Assert.IsFalse(document.IsCultureEdited("fr"));
Assert.IsFalse(document.Edited);
document.SetValue("value1", "v1en"); //change the property value, so now the invariant (default) culture will be edited
ServiceContext.ContentService.Save(document);
document = ServiceContext.ContentService.GetById(document.Id);
Assert.AreEqual("doc1en", document.Name);
Assert.AreEqual("doc1en", document.GetCultureName("en"));
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
Assert.AreEqual("v1en", document.GetValue("value1"));
Assert.AreEqual("v1en-init", document.GetValue("value1", published: true));
Assert.IsTrue(document.IsCultureEdited("en")); //This is true because the invariant property reflects changes on the default lang
Assert.IsFalse(document.IsCultureEdited("fr"));
Assert.IsTrue(document.Edited);
// switch property type to Culture
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag
document = ServiceContext.ContentService.GetById(document.Id);
Assert.IsTrue(document.IsCultureEdited("en")); //Remains true
Assert.IsFalse(document.IsCultureEdited("fr")); //False because no french property has ever been edited
Assert.IsTrue(document.Edited);
//update the culture value and publish
document.SetValue("value1", "v1en2", "en");
ServiceContext.ContentService.SaveAndPublish(document);
document = ServiceContext.ContentService.GetById(document.Id);
Assert.AreEqual("doc1en", document.Name);
Assert.AreEqual("doc1en", document.GetCultureName("en"));
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
Assert.IsNull(document.GetValue("value1")); //The values are there but the business logic returns null
Assert.IsNull(document.GetValue("value1", published: true)); //The values are there but the business logic returns null
Assert.AreEqual("v1en2", document.GetValue("value1", "en"));
Assert.AreEqual("v1en2", document.GetValue("value1", "en", published: true));
Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published
Assert.IsFalse(document.IsCultureEdited("fr")); //False because no french property has ever been edited
Assert.IsFalse(document.Edited);
// switch property back to Invariant
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
document = ServiceContext.ContentService.GetById(document.Id);
Assert.AreEqual("v1en2", document.GetValue("value1")); //The variant property value gets copied over to the invariant
Assert.AreEqual("v1en2", document.GetValue("value1", published: true));
Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null
Assert.IsNull(document.GetValue("value1", "fr", published: true)); //The values are there but the business logic returns null
Assert.IsFalse(document.IsCultureEdited("en")); //The variant published AND edited values are copied over to the invariant
Assert.IsFalse(document.IsCultureEdited("fr"));
Assert.IsFalse(document.Edited);
}
[Test]
public void Change_Variations_ComposedContentType_1()
{
@@ -437,59 +882,22 @@ namespace Umbraco.Tests.Services
// can change the composing content type to invariant and back
// can change the composed content type to invariant and back
var languageEn = new Language("en") { IsDefault = true };
ServiceContext.LocalizationService.Save(languageEn);
var languageFr = new Language("fr");
ServiceContext.LocalizationService.Save(languageFr);
CreateFrenchAndEnglishLangs();
var composing = new ContentType(-1)
{
Alias = "composing",
Name = "composing",
Variations = ContentVariation.Culture
};
var composing = CreateContentType(ContentVariation.Culture, "composing");
var properties1 = new PropertyTypeCollection(true)
{
new PropertyType("value11", ValueStorageType.Ntext)
{
Alias = "value11",
DataTypeId = -88,
Variations = ContentVariation.Culture
},
new PropertyType("value12", ValueStorageType.Ntext)
{
Alias = "value12",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties1 = CreatePropertyCollection(
("value11", ContentVariation.Culture),
("value12", ContentVariation.Nothing));
composing.PropertyGroups.Add(new PropertyGroup(properties1) { Name = "Content" });
ServiceContext.ContentTypeService.Save(composing);
var composed = new ContentType(-1)
{
Alias = "composed",
Name = "composed",
Variations = ContentVariation.Culture
};
var composed = CreateContentType(ContentVariation.Culture, "composed");
var properties2 = new PropertyTypeCollection(true)
{
new PropertyType("value21", ValueStorageType.Ntext)
{
Alias = "value21",
DataTypeId = -88,
Variations = ContentVariation.Culture
},
new PropertyType("value22", ValueStorageType.Ntext)
{
Alias = "value22",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties2 = CreatePropertyCollection(
("value21", ContentVariation.Culture),
("value22", ContentVariation.Nothing));
composed.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" });
composed.AddContentType(composing);
@@ -569,86 +977,32 @@ namespace Umbraco.Tests.Services
// can change the composing content type to invariant and back
// can change the variant composed content type to invariant and back
var languageEn = new Language("en") { IsDefault = true };
ServiceContext.LocalizationService.Save(languageEn);
var languageFr = new Language("fr");
ServiceContext.LocalizationService.Save(languageFr);
CreateFrenchAndEnglishLangs();
var composing = new ContentType(-1)
{
Alias = "composing",
Name = "composing",
Variations = ContentVariation.Culture
};
var composing = CreateContentType(ContentVariation.Culture, "composing");
var properties1 = new PropertyTypeCollection(true)
{
new PropertyType("value11", ValueStorageType.Ntext)
{
Alias = "value11",
DataTypeId = -88,
Variations = ContentVariation.Culture
},
new PropertyType("value12", ValueStorageType.Ntext)
{
Alias = "value12",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties1 = CreatePropertyCollection(
("value11", ContentVariation.Culture),
("value12", ContentVariation.Nothing));
composing.PropertyGroups.Add(new PropertyGroup(properties1) { Name = "Content" });
ServiceContext.ContentTypeService.Save(composing);
var composed1 = new ContentType(-1)
{
Alias = "composed1",
Name = "composed1",
Variations = ContentVariation.Culture
};
var composed1 = CreateContentType(ContentVariation.Culture, "composed1");
var properties2 = new PropertyTypeCollection(true)
{
new PropertyType("value21", ValueStorageType.Ntext)
{
Alias = "value21",
DataTypeId = -88,
Variations = ContentVariation.Culture
},
new PropertyType("value22", ValueStorageType.Ntext)
{
Alias = "value22",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties2 = CreatePropertyCollection(
("value21", ContentVariation.Culture),
("value22", ContentVariation.Nothing));
composed1.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" });
composed1.AddContentType(composing);
ServiceContext.ContentTypeService.Save(composed1);
var composed2 = new ContentType(-1)
{
Alias = "composed2",
Name = "composed2",
Variations = ContentVariation.Nothing
};
var composed2 = CreateContentType(ContentVariation.Nothing, "composed2");
var properties3 = new PropertyTypeCollection(true)
{
new PropertyType("value31", ValueStorageType.Ntext)
{
Alias = "value31",
DataTypeId = -88,
Variations = ContentVariation.Nothing
},
new PropertyType("value32", ValueStorageType.Ntext)
{
Alias = "value32",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties3 = CreatePropertyCollection(
("value31", ContentVariation.Nothing),
("value32", ContentVariation.Nothing));
composed2.PropertyGroups.Add(new PropertyGroup(properties3) { Name = "Content" });
composed2.AddContentType(composing);
@@ -754,5 +1108,35 @@ namespace Umbraco.Tests.Services
AssertJsonStartsWith(document2.Id,
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
}
private void CreateFrenchAndEnglishLangs()
{
var languageEn = new Language("en") { IsDefault = true };
ServiceContext.LocalizationService.Save(languageEn);
var languageFr = new Language("fr");
ServiceContext.LocalizationService.Save(languageFr);
}
private IContentType CreateContentType(ContentVariation variance, string alias = "contentType") => new ContentType(-1)
{
Alias = alias,
Name = alias,
Variations = variance
};
private PropertyTypeCollection CreatePropertyCollection(params (string alias, ContentVariation variance)[] props)
{
var propertyCollection = new PropertyTypeCollection(true);
foreach (var (alias, variance) in props)
propertyCollection.Add(new PropertyType(alias, ValueStorageType.Ntext)
{
Alias = alias,
DataTypeId = -88,
Variations = variance
});
return propertyCollection;
}
}
}
@@ -2,6 +2,7 @@
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using Umbraco.Core.Exceptions;
namespace Umbraco.Tests.Testing
{
@@ -29,7 +30,7 @@ namespace Umbraco.Tests.Testing
var methodName = test.MethodName;
var type = Type.GetType(typeName, true);
if (type == null)
throw new Exception("panic"); // makes no sense
throw new PanicException($"Could not resolve the type from type name {typeName}"); // makes no sense
var methodInfo = type.GetMethod(methodName); // what about overloads?
var options = GetTestOptions<TOptions>(methodInfo);
return options;
@@ -53,7 +54,7 @@ namespace Umbraco.Tests.Testing
{
if (other == null) throw new ArgumentNullException(nameof(other));
if (!(Merge((TestOptionAttributeBase) other) is TOptions merged))
throw new Exception("panic");
throw new PanicException("Could not merge test options");
return merged;
}
+23 -5
View File
@@ -16,13 +16,31 @@ module.exports = {
//processed in the js task
js: {
preview: { files: ["./src/preview/**/*.js"], out: "umbraco.preview.js" },
installer: { files: ["./src/installer/**/*.js"], out: "umbraco.installer.js" },
controllers: { files: ["./src/{views,controllers}/**/*.controller.js"], out: "umbraco.controllers.js" },
directives: { files: ["./src/common/directives/**/*.js"], out: "umbraco.directives.js" },
installer: { files: ["./src/installer/**/*.js"], out: "umbraco.installer.js" },
filters: { files: ["./src/common/filters/**/*.js"], out: "umbraco.filters.js" },
resources: { files: ["./src/common/resources/**/*.js"], out: "umbraco.resources.js" },
services: { files: ["./src/common/services/**/*.js"], out: "umbraco.services.js" },
security: { files: ["./src/common/interceptors/**/*.js"], out: "umbraco.interceptors.js" }
security: { files: ["./src/common/interceptors/**/*.js"], out: "umbraco.interceptors.js" },
//the controllers for views
controllers: {
files: [
"./src/views/**/*.controller.js",
"./src/*.controller.js"
], out: "umbraco.controllers.js"
},
//directives/components
// - any JS file found in common / directives or common/ components
// - any JS file found inside views that has the suffix .directive.js or .component.js
directives: {
files: [
"./src/common/directives/_module.js",
"./src/{common/directives,common/components}/**/*.js",
"./src/views/**/*.{directive,component}.js"
],
out: "umbraco.directives.js"
},
},
//selectors for copying all views into the build
@@ -34,7 +52,7 @@ module.exports = {
//globs for file-watching
globs:{
views: "./src/views/**/*.html",
views: ["./src/views/**/*.html", "./src/common/directives/**/*.html", "./src/common/components/**/*.html" ],
less: "./src/less/**/*.less",
js: "./src/*.js",
lib: "./lib/**/*",
+4 -1
View File
@@ -5,6 +5,9 @@ var gulp = require('gulp');
var runSequence = require('run-sequence');
// Dev - build the files ready for development and start watchers
gulp.task('dev', function(cb) {
gulp.task('dev', function (cb) {
global.isProd = false;
runSequence(["dependencies", "js", "less", "views"], "watch", cb);
});
@@ -1,13 +0,0 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var runSequence = require('run-sequence');
// Dev - build the files ready for development and start watchers
gulp.task('fastdev', function(cb) {
global.isProd = false;
runSequence(["dependencies", "js", "less", "views"], "watch", cb);
});
@@ -9,26 +9,28 @@ var concat = require('gulp-concat');
var wrap = require("gulp-wrap-js");
var embedTemplates = require('gulp-angular-embed-templates');
module.exports = function(files, out) {
module.exports = function (files, out) {
var task = gulp.src(files);
if (global.isProd === true) {
// check for js errors
task = task.pipe(eslint());
// outputs the lint results to the console
task = task.pipe(eslint.format());
}
// check for js errors
task = task.pipe(eslint());
// outputs the lint results to the console
task = task.pipe(eslint.format());
// sort files in stream by path or any custom sort comparator
task = task.pipe(babel())
.pipe(sort())
.pipe(embedTemplates({ basePath: "./src/", minimize:{ loose: true } }))
.pipe(concat(out))
.pipe(sort());
if (global.isProd === true) {
//in production, embed the templates
task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } }))
}
task = task.pipe(concat(out))
.pipe(wrap('(function(){\n%= body %\n})();'))
.pipe(gulp.dest(config.root + config.targets.js));
return task;
};
@@ -17,17 +17,12 @@ module.exports = function(files, out) {
];
var task = gulp.src(files)
.pipe(less());
if (global.isProd === true) {
task = task.pipe(cleanCss());
}
task = task.pipe(postcss(processors))
.pipe(less())
.pipe(cleanCss())
.pipe(postcss(processors))
.pipe(rename(out))
.pipe(gulp.dest(config.root + config.targets.css));
return task;
};
};
@@ -1239,6 +1239,11 @@
// Perform the button's action.
function doClick(button) {
// don't do anything if the editor input or button bar isn't the currently active element
if (document.activeElement !== panels.input && !panels.buttonBar.contains(document.activeElement)) {
return;
}
inputBox.focus();
if (button.textOp) {
File diff suppressed because it is too large Load Diff
@@ -21,6 +21,12 @@
$scope.page.menu.currentSection = appState.getSectionState("currentSection");
$scope.page.listViewPath = null;
$scope.page.isNew = $scope.isNew ? true : false;
if (infiniteMode) {
$scope.page.allowInfinitePublishAndClose = $scope.infiniteModel.allowPublishAndClose;
$scope.page.allowInfiniteSaveAndClose = $scope.infiniteModel.allowSaveAndClose;
}
$scope.page.buttonGroupState = "init";
$scope.page.hideActionsMenu = infiniteMode ? true : false;
$scope.page.hideChangeVariant = false;
@@ -369,12 +375,17 @@
saveMethod: args.saveMethod,
scope: $scope,
content: $scope.content,
create: $scope.page.isNew,
action: args.action,
showNotifications: args.showNotifications,
softRedirect: true
}).then(function (data) {
//success
init();
//needs to be manually set for infinite editing mode
$scope.page.isNew = false;
syncTreeNode($scope.content, data.path);
eventsService.emit("content.saved", { content: $scope.content, action: args.action });
@@ -11,6 +11,11 @@ function hexBgColor() {
restrict: "A",
link: function (scope, element, attr, formCtrl) {
function setBackgroundColor(color) {
// note: can't use element.css(), it doesn't support hexa background colors
angular.element(element)[0].style.backgroundColor = "#" + color;
}
// Only add inline hex background color if defined and not "true".
if (attr.hexBgInline === undefined || (attr.hexBgInline !== undefined && attr.hexBgInline === "true")) {
@@ -26,18 +31,21 @@ function hexBgColor() {
// Get the orig color before changing it.
origColor = element.css("border-color");
}
// Validate it - test with and without the leading hash.
if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
element.css("background-color", "#" + newVal);
// Is it a regular hex value - (#)AABBCC ?
var match = newVal.match(/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i);
if (match && match.length) {
setBackgroundColor(match[1]);
return;
}
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
element.css("background-color", newVal);
// Is it a hexa value - (#)AABBCCDD ?
match = newVal.match(/^#?([0-9a-f]{4}|[0-9a-f]{8})$/i);
if (match && match.length) {
setBackgroundColor(match[1]);
return;
}
}
element.css("background-color", origColor);
setBackgroundColor(origColor);
});
}
}
@@ -14,6 +14,8 @@ angular.module("umbraco.directives")
// TODO: A lot of the code below should be shared between the grid rte and the normal rte
scope.isLoading = true;
var promises = [];
//To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias
@@ -91,6 +93,10 @@ angular.module("umbraco.directives")
//custom initialization for this editor within the grid
editor.on('init', function (e) {
// Used this init event - as opposed to property init_instance_callback
// to turn off the loader
scope.isLoading = false;
//force overflow to hidden to prevent no needed scroll
editor.getBody().style.overflow = "hidden";
@@ -1,7 +1,7 @@
(function () {
'use strict';
function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService) {
function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper) {
function link(scope, element, attrs, ctrl) {
@@ -28,6 +28,9 @@
// make sure dates are formatted to the user's locale
formatDatesToLocal();
// set media file extension initially
setMediaExtension();
}
function formatDatesToLocal() {
@@ -49,6 +52,10 @@
}
}
function setMediaExtension() {
scope.node.extension = mediaHelper.getFileExtension(scope.nodeUrl);
}
scope.openMediaType = function (mediaType) {
var editor = {
id: mediaType.id,
@@ -62,6 +69,16 @@
editorService.mediaTypeEditor(editor);
};
scope.openSVG = function () {
var popup = window.open('', '_blank');
var html = '<!DOCTYPE html><body><img src="' + scope.nodeUrl + '"/>' +
'<script>history.pushState(null, null,"' + $location.$$absUrl + '");</script></body>';
popup.document.open();
popup.document.write(html);
popup.document.close();
}
// watch for content updates - reload content when node is saved, published etc.
scope.$watch('node.updateDate', function(newValue, oldValue){
if(!newValue) { return; }
@@ -72,6 +89,9 @@
// Update the create and update dates
formatDatesToLocal();
//Update the media file format
setMediaExtension();
});
//ensure to unregister from all events!
@@ -1,13 +1,13 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbFlatpickr
@name umbraco.directives.directive:umbDateTimePicker
@restrict E
@scope
@description
<b>Added in Umbraco version 8.0</b>
This directive is a wrapper of the flatpickr library. Use it to render a date time picker.
For extra details about options and events take a look here: https://flatpickr.js.org/
For extra details about options and events take a look here: https://flatpickr.js.org
Use this directive to render a date time picker
@@ -15,11 +15,11 @@ Use this directive to render a date time picker
<pre>
<div ng-controller="My.Controller as vm">
<umb-flatpickr
<umb-date-time-picker
ng-model="vm.date"
options="vm.config"
on-change="vm.datePickerChange(selectedDates, dateStr, instance)">
</umb-flatpickr>
</umb-date-time-picker>
</div>
</pre>
@@ -70,12 +70,12 @@ Use this directive to render a date time picker
(function() {
'use strict';
var umbFlatpickr = {
var umbDateTimePicker = {
template: '<ng-transclude>' +
'<input type="text" ng-if="!$ctrl.options.inline" ng-model="$ctrl.ngModel" placeholder="Select Date.."></input>' +
'<div ng-if="$ctrl.options.inline"></div>' +
'</ng-transclude>',
controller: umbFlatpickrCtrl,
controller: umbDateTimePickerCtrl,
transclude: true,
bindings: {
ngModel: '<',
@@ -92,9 +92,9 @@ Use this directive to render a date time picker
}
};
function umbFlatpickrCtrl($element, $timeout, $scope, assetsService, userService) {
function umbDateTimePickerCtrl($element, $timeout, $scope, assetsService, userService) {
var ctrl = this;
var loaded = false;
var userLocale = null;
ctrl.$onInit = function() {
@@ -102,14 +102,14 @@ Use this directive to render a date time picker
// load css file for the date picker
assetsService.loadCss('lib/flatpickr/flatpickr.css', $scope).then(function () {
userService.getCurrentUser().then(function (user) {
// init date picker
userLocale = user.locale;
if (userLocale.indexOf('-') > -1) {
userLocale = userLocale.split('-')[0];
}
loaded = true;
grabElementAndRunFlatpickr();
grabElementAndRunFlatpickr();
});
});
@@ -234,7 +234,8 @@ Use this directive to render a date time picker
}
}
angular.module('umbraco.directives').component('umbFlatpickr', umbFlatpickr);
// umbFlatpickr (umb-flatpickr) is deprecated, but we keep it for backwards compatibility
angular.module('umbraco.directives').component('umbFlatpickr', umbDateTimePicker);
angular.module('umbraco.directives').component('umbDateTimePicker', umbDateTimePicker);
})();
@@ -17,6 +17,7 @@
scope.sortableOptionsGroup = {};
scope.sortableOptionsProperty = {};
scope.sortingButtonKey = "general_reorder";
scope.compositionsButtonState = "init";
function activate() {
@@ -47,6 +48,7 @@
function setSortingOptions() {
scope.sortableOptionsGroup = {
axis: 'y',
distance: 10,
tolerance: "pointer",
opacity: 0.7,
@@ -65,6 +67,7 @@
};
scope.sortableOptionsProperty = {
axis: 'y',
distance: 10,
tolerance: "pointer",
connectWith: ".umb-group-builder__properties",
@@ -335,6 +338,7 @@
})), function(f) {
return f !== null && f !== undefined;
});
scope.compositionsButtonState = "busy";
$q.all([
//get available composite types
availableContentTypeResource(scope.model.id, [], propAliasesExisting).then(function (result) {
@@ -354,6 +358,7 @@
]).then(function() {
//resolves when both other promises are done, now show it
editorService.open(scope.compositionsDialogModel);
scope.compositionsButtonState = "init";
});
};
@@ -530,6 +535,7 @@
// push new init tab to the scope
addInitGroup(scope.model.groups);
notifyChanged();
},
close: function() {
if(_.isEqual(oldPropertyModel, propertyModel) === false) {
@@ -586,8 +592,13 @@
}
notifyChanged();
};
function notifyChanged() {
eventsService.emit("editors.groupsBuilder.changed");
}
function addInitProperty(group) {
var addInitPropertyBool = true;
@@ -0,0 +1,75 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbLoader
@restrict E
@description
Use this directive to generate a loading indicator.
<h3>Markup example</h3>
<pre>
<div ng-controller="My.Controller as vm">
<umb-loader
ng-if="vm.loading">
</umb-loader>
<div class="content" ng-if="!vm.loading">
<p>{{content}}</p>
</div>
</div>
</pre>
<h3>Controller example</h3>
<pre>
(function () {
"use strict";
function Controller(myService) {
var vm = this;
vm.content = "";
vm.loading = true;
myService.getContent().then(function(content){
vm.content = content;
vm.loading = false;
});
}
angular.module("umbraco").controller("My.Controller", Controller);
})();
</pre>
@param {string=} position The loader position ("top", "bottom").
**/
(function() {
'use strict';
function UmbLoaderDirective() {
function link(scope, el, attr, ctrl) {
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/umb-loader.html',
scope: {
position: "@?"
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbLoader', UmbLoaderDirective);
})();
@@ -39,7 +39,7 @@ Use this directive to generate a loading indicator.
});
}
½
angular.module("umbraco").controller("My.Controller", Controller);
})();
</pre>
@@ -1,167 +1,172 @@
(function () {
'use strict';
'use strict';
function ChangePasswordController($scope) {
function ChangePasswordController($scope) {
function resetModel(isNew) {
//the model config will contain an object, if it does not we'll create defaults
//NOTE: We will not support doing the password regex on the client side because the regex on the server side
//based on the membership provider cannot always be ported to js from .net directly.
/*
{
hasPassword: true/false,
requiresQuestionAnswer: true/false,
enableReset: true/false,
enablePasswordRetrieval: true/false,
minPasswordLength: 10
}
*/
var vm = this;
$scope.showReset = false;
vm.$onInit = onInit;
vm.$onDestroy = onDestroy;
vm.doChange = doChange;
vm.cancelChange = cancelChange;
vm.showOldPass = showOldPass;
vm.showCancelBtn = showCancelBtn;
//set defaults if they are not available
if ($scope.config.disableToggle === undefined) {
$scope.config.disableToggle = false;
}
if ($scope.config.hasPassword === undefined) {
$scope.config.hasPassword = false;
}
if ($scope.config.enablePasswordRetrieval === undefined) {
$scope.config.enablePasswordRetrieval = true;
}
if ($scope.config.requiresQuestionAnswer === undefined) {
$scope.config.requiresQuestionAnswer = false;
}
//don't enable reset if it is new - that doesn't make sense
if (isNew === "true") {
$scope.config.enableReset = false;
}
else if ($scope.config.enableReset === undefined) {
$scope.config.enableReset = true;
}
if ($scope.config.minPasswordLength === undefined) {
$scope.config.minPasswordLength = 0;
}
//set the model defaults
if (!angular.isObject($scope.passwordValues)) {
//if it's not an object then just create a new one
$scope.passwordValues = {
newPassword: null,
oldPassword: null,
reset: null,
answer: null
};
}
else {
//just reset the values
var unsubscribe = [];
function resetModel(isNew) {
//the model config will contain an object, if it does not we'll create defaults
//NOTE: We will not support doing the password regex on the client side because the regex on the server side
//based on the membership provider cannot always be ported to js from .net directly.
/*
{
hasPassword: true/false,
requiresQuestionAnswer: true/false,
enableReset: true/false,
enablePasswordRetrieval: true/false,
minPasswordLength: 10
}
*/
vm.showReset = false;
//set defaults if they are not available
if (vm.config.disableToggle === undefined) {
vm.config.disableToggle = false;
}
if (vm.config.hasPassword === undefined) {
vm.config.hasPassword = false;
}
if (vm.config.enablePasswordRetrieval === undefined) {
vm.config.enablePasswordRetrieval = true;
}
if (vm.config.requiresQuestionAnswer === undefined) {
vm.config.requiresQuestionAnswer = false;
}
//don't enable reset if it is new - that doesn't make sense
if (isNew === "true") {
vm.config.enableReset = false;
}
else if (vm.config.enableReset === undefined) {
vm.config.enableReset = true;
}
if (vm.config.minPasswordLength === undefined) {
vm.config.minPasswordLength = 0;
}
//set the model defaults
if (!angular.isObject(vm.passwordValues)) {
//if it's not an object then just create a new one
vm.passwordValues = {
newPassword: null,
oldPassword: null,
reset: null,
answer: null
};
}
else {
//just reset the values
if (!isNew) {
//if it is new, then leave the generated pass displayed
vm.passwordValues.newPassword = null;
vm.passwordValues.oldPassword = null;
}
vm.passwordValues.reset = null;
vm.passwordValues.answer = null;
}
//the value to compare to match passwords
if (!isNew) {
vm.passwordValues.confirm = "";
}
else if (vm.passwordValues.newPassword && vm.passwordValues.newPassword.length > 0) {
//if it is new and a new password has been set, then set the confirm password too
vm.passwordValues.confirm = vm.passwordValues.newPassword;
}
if (!isNew) {
//if it is new, then leave the generated pass displayed
$scope.passwordValues.newPassword = null;
$scope.passwordValues.oldPassword = null;
}
$scope.passwordValues.reset = null;
$scope.passwordValues.answer = null;
}
//the value to compare to match passwords
if (!isNew) {
$scope.passwordValues.confirm = "";
}
else if ($scope.passwordValues.newPassword && $scope.passwordValues.newPassword.length > 0) {
//if it is new and a new password has been set, then set the confirm password too
$scope.passwordValues.confirm = $scope.passwordValues.newPassword;
}
//when the scope is destroyed we need to unsubscribe
function onDestroy() {
for (var u in unsubscribe) {
unsubscribe[u]();
}
}
function onInit() {
//listen for the saved event, when that occurs we'll
//change to changing = false;
unsubscribe.push($scope.$on("formSubmitted", function () {
if (vm.config.disableToggle === false) {
vm.changing = false;
}
}));
unsubscribe.push($scope.$on("formSubmitting", function () {
//if there was a previously generated password displaying, clear it
if (vm.changing && vm.passwordValues) {
vm.passwordValues.generatedPassword = null;
}
else if (!vm.changing) {
//we are not changing, so the model needs to be null
vm.passwordValues = null;
}
}));
resetModel(vm.isNew);
//if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there
//with validators turned on.
vm.changing = vm.config.disableToggle === true || !vm.config.hasPassword;
//we're not currently changing so set the model to null
if (!vm.changing) {
vm.passwordValues = null;
}
}
function doChange() {
resetModel();
vm.changing = true;
//if there was a previously generated password displaying, clear it
vm.passwordValues.generatedPassword = null;
vm.passwordValues.confirm = null;
};
function cancelChange() {
vm.changing = false;
//set model to null
vm.passwordValues = null;
};
function showOldPass() {
return vm.config.hasPassword &&
!vm.config.allowManuallyChangingPassword &&
!vm.config.enablePasswordRetrieval && !vm.showReset;
};
// TODO: I don't think we need this or the cancel button, this can be up to the editor rendering this component
function showCancelBtn() {
return vm.config.disableToggle !== true && vm.config.hasPassword;
};
}
resetModel($scope.isNew);
//if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there
//with validators turned on.
$scope.changing = $scope.config.disableToggle === true || !$scope.config.hasPassword;
//we're not currently changing so set the model to null
if (!$scope.changing) {
$scope.passwordValues = null;
}
$scope.doChange = function () {
resetModel();
$scope.changing = true;
//if there was a previously generated password displaying, clear it
$scope.passwordValues.generatedPassword = null;
$scope.passwordValues.confirm = null;
var component = {
templateUrl: 'views/components/users/change-password.html',
controller: ChangePasswordController,
controllerAs: 'vm',
bindings: {
isNew: "<",
passwordValues: "=", //TODO: Do we need bi-directional vals?
config: "=" //TODO: Do we need bi-directional vals?
//TODO: Do we need callbacks?
}
};
$scope.cancelChange = function () {
$scope.changing = false;
//set model to null
$scope.passwordValues = null;
};
var unsubscribe = [];
//listen for the saved event, when that occurs we'll
//change to changing = false;
unsubscribe.push($scope.$on("formSubmitted", function () {
if ($scope.config.disableToggle === false) {
$scope.changing = false;
}
}));
unsubscribe.push($scope.$on("formSubmitting", function () {
//if there was a previously generated password displaying, clear it
if ($scope.changing && $scope.passwordValues) {
$scope.passwordValues.generatedPassword = null;
}
else if (!$scope.changing) {
//we are not changing, so the model needs to be null
$scope.passwordValues = null;
}
}));
//when the scope is destroyed we need to unsubscribe
$scope.$on('$destroy', function () {
for (var u in unsubscribe) {
unsubscribe[u]();
}
});
$scope.showOldPass = function () {
return $scope.config.hasPassword &&
!$scope.config.allowManuallyChangingPassword &&
!$scope.config.enablePasswordRetrieval && !$scope.showReset;
};
// TODO: I don't think we need this or the cancel button, this can be up to the editor rendering this directive
$scope.showCancelBtn = function () {
return $scope.config.disableToggle !== true && $scope.config.hasPassword;
};
}
function ChangePasswordDirective() {
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/users/change-password.html',
controller: 'Umbraco.Editors.Users.ChangePasswordDirectiveController',
scope: {
isNew: "=?",
passwordValues: "=",
config: "="
}
};
return directive;
}
angular.module('umbraco.directives').controller('Umbraco.Editors.Users.ChangePasswordDirectiveController', ChangePasswordController);
angular.module('umbraco.directives').directive('changePassword', ChangePasswordDirective);
angular.module('umbraco.directives').component('changePassword', component);
})();
@@ -104,21 +104,26 @@ function entityResource($q, $http, umbRequestHelper) {
*
* @param {Int} id Id of node to return the public url to
* @param {string} type Object type name
* @param {string} culture Culture
* @returns {Promise} resourcePromise object containing the url.
*
*/
getUrl: function (id, type) {
getUrl: function (id, type, culture) {
if (id === -1 || id === "-1") {
return "";
}
if (!culture) {
culture = "";
}
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"entityApiBaseUrl",
"GetUrl",
[{ id: id }, {type: type }])),
[{ id: id }, {type: type }, {culture: culture }])),
'Failed to retrieve url for id:' + id);
},
@@ -7,64 +7,44 @@
**/
function logViewerResource($q, $http, umbRequestHelper) {
/**
* verb => 'get', 'post',
* method => API method to call
* params => additional data to send
* error => error message when things go wrong...
*/
const request = (verb, method, params, error) =>
umbRequestHelper.resourcePromise(
(verb === 'GET' ?
$http.get(umbRequestHelper.getApiUrl("logViewerApiBaseUrl", method) + (params ? params : '')) :
$http.post(umbRequestHelper.getApiUrl("logViewerApiBaseUrl", method), params)),
error);
//the factory object returned
return {
getNumberOfErrors: function (startDate, endDate) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"logViewerApiBaseUrl",
"GetNumberOfErrors")+ '?startDate='+startDate+ '&endDate='+ endDate ),
'Failed to retrieve number of errors in logs');
},
getNumberOfErrors: (startDate, endDate) =>
request('GET', 'GetNumberOfErrors', '?startDate=' + startDate + '&endDate=' + endDate, 'Failed to retrieve number of errors in logs'),
getLogLevelCounts: function (startDate, endDate) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"logViewerApiBaseUrl",
"GetLogLevelCounts")+ '?startDate='+startDate+ '&endDate='+ endDate ),
'Failed to retrieve log level counts');
},
getLogLevel: () =>
request('GET', 'GetLogLevel', null, 'Failed to retrieve log level'),
getMessageTemplates: function (startDate, endDate) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"logViewerApiBaseUrl",
"GetMessageTemplates")+ '?startDate='+startDate+ '&endDate='+ endDate ),
'Failed to retrieve log templates');
},
getLogLevelCounts: (startDate, endDate) =>
request('GET', 'GetLogLevelCounts', '?startDate=' + startDate + '&endDate=' + endDate, 'Failed to retrieve log level counts'),
getSavedSearches: function () {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"logViewerApiBaseUrl",
"GetSavedSearches")),
'Failed to retrieve saved searches');
},
getMessageTemplates: (startDate, endDate) =>
request('GET', 'GetMessageTemplates', '?startDate=' + startDate + '&endDate=' + endDate, 'Failed to retrieve log templates'),
postSavedSearch: function (name, query) {
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"logViewerApiBaseUrl",
"PostSavedSearch"), { 'name': name, 'query': query }),
'Failed to add new saved search');
},
getSavedSearches: () =>
request('GET', 'GetSavedSearches', null, 'Failed to retrieve saved searches'),
deleteSavedSearch: function (name, query) {
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"logViewerApiBaseUrl",
"DeleteSavedSearch"), { 'name': name, 'query': query }),
'Failed to delete saved search');
},
postSavedSearch: (name, query) =>
request('POST', 'PostSavedSearch', { 'name': name, 'query': query }, 'Failed to add new saved search'),
getLogs: function (options) {
deleteSavedSearch: (name, query) =>
request('POST', 'DeleteSavedSearch', { 'name': name, 'query': query }, 'Failed to delete saved search'),
getLogs: options => {
var defaults = {
pageSize: 100,
@@ -83,7 +63,6 @@ function logViewerResource($q, $http, umbRequestHelper) {
//now copy back to the options we will use
options = defaults;
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
@@ -93,15 +72,8 @@ function logViewerResource($q, $http, umbRequestHelper) {
'Failed to retrieve common log messages');
},
canViewLogs: function (startDate, endDate) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"logViewerApiBaseUrl",
"GetCanViewLogs") + '?startDate='+startDate+ '&endDate='+ endDate ),
'Failed to retrieve state if logs can be viewed');
}
canViewLogs: (startDate, endDate) =>
request('GET', 'GetCanViewLogs', '?startDate=' + startDate + '&endDate=' + endDate, 'Failed to retrieve state if logs can be viewed')
};
}
@@ -1,4 +1,4 @@
/**
* @ngdoc service
* @name umbraco.services.contentEditingHelper
@@ -53,6 +53,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
}
if (args.showNotifications === undefined) {
args.showNotifications = true;
}
// needed for infinite editing to create new items
if (args.create === undefined) {
if ($routeParams.create) {
args.create = true;
}
}
if (args.softRedirect === undefined) {
//when true, the url will change but it won't actually re-route
@@ -61,6 +67,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
args.softRedirect = false;
}
var self = this;
//we will use the default one for content if not specified
@@ -68,7 +75,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
if (formHelper.submitForm({ scope: args.scope, action: args.action })) {
return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles(), args.showNotifications)
return args.saveMethod(args.content, args.create, fileManager.getFiles(), args.showNotifications)
.then(function (data) {
formHelper.resetForm({ scope: args.scope });
@@ -484,7 +491,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
var savedVariants = [];
if (origContent.variants) {
isContent = true;
//it's contnet so assign the variants as they exist
//it's content so assign the variants as they exist
origVariants = origContent.variants;
savedVariants = savedContent.variants;
}
@@ -510,7 +517,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
//special case for content, don't sync this variant if it wasn't tagged
//for saving in the first place
if (!origVariant.save) {
if (isContent && !origVariant.save) {
continue;
}
@@ -331,13 +331,17 @@ When building a custom infinite editor view you can use the same components as a
* @methodOf umbraco.services.editorService
*
* @description
* Opens a media editor in infinite editing, the submit callback returns the updated content item
* Opens a content editor in infinite editing, the submit callback returns the updated content item
* @param {Object} editor rendering options
* @param {String} editor.id The id of the content item
* @param {Boolean} editor.create Create new content item
* @param {Function} editor.submit Callback function when the publish and close button is clicked. Returns the editor model object
* @param {Function} editor.close Callback function when the close button is clicked.
*
* @param {String} editor.parentId If editor.create is true, provide parentId for the creation of the content item
* @param {String} editor.documentTypeAlias If editor.create is true, provide document type alias for the creation of the content item
* @param {Boolean} editor.allowSaveAndClose If editor is being used in infinite editing allows the editor to close when the save action is performed
* @param {Boolean} editor.allowPublishAndClose If editor is being used in infinite editing allows the editor to close when the publish action is performed
*
* @returns {Object} editor object
*/
function contentEditor(editor) {
@@ -112,7 +112,7 @@
@import "components/umb-editor-navigation-item.less";
@import "components/umb-editor-sub-views.less";
@import "components/editor/subheader/umb-editor-sub-header.less";
@import "components/umb-flatpickr.less";
@import "components/umb-date-time-picker.less";
@import "components/umb-grid-selector.less";
@import "components/umb-child-selector.less";
@import "components/umb-group-builder.less";
@@ -124,6 +124,7 @@
@import "components/umb-form-check.less";
@import "components/umb-locked-field.less";
@import "components/umb-tabs.less";
@import "components/umb-loader.less";
@import "components/umb-load-indicator.less";
@import "components/umb-breadcrumbs.less";
@import "components/umb-media-grid.less";
@@ -1,8 +1,12 @@
.umb-tour__loader {
background: @white;
z-index: @zindexTourModal;
.umb-loader-wrapper.umb-tour__loader {
margin: 0;
position: fixed;
height: 5px;
z-index: @zindexTourModal;
.umb-loader {
background-color: @white;
height: 5px;
}
}
.umb-tour__pulse {
@@ -74,8 +74,11 @@
}
.umb-editor-header__back {
color: @gray-6;
margin-bottom: 1px;
background: transparent;
border: 0;
color: @gray-6;
margin: 0 0 1px 0;
padding: 0;
transition: color 0.1s ease-in-out;
}
@@ -373,14 +376,3 @@ a.umb-variant-switcher__toggle {
margin-right: auto;
padding-right: 10px;
}
/* Confirm */
.umb-editor-confirm {
background-color: @white;
padding: 20px;
position: absolute;
left: 0;
bottom: 0;
z-index: 10;
box-shadow: 0 -3px 12px 0px rgba(0,0,0,0.16);
}
@@ -1,5 +1,5 @@
.umb-notifications {
z-index: 1000;
z-index: 1100;
position: absolute;
bottom: @editorFooterHeight;
left: 0;
@@ -39,17 +39,10 @@
// Loading Animation
// ------------------------
.l {
.umb-tree-item__loader {
width: 100%;
height: 2px;
overflow: hidden;
position: absolute;
left: 0;
bottom: -1px;
div {
.umb-loader;
}
margin: 0;
}
.umb-tree-item__label {
@@ -57,19 +50,20 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1 0 auto;
flex: 1 1 auto;
}
}
// active is equivilant to selected, its the item that is begin affected by the actions performed in the right-click-dialog.
.umb-tree-item.active > .umb-tree-item__inner {
border-color: @ui-selected-border;
box-shadow: 0 0 2px 0 fade(@ui-selected-border, 80%);
color: @ui-selected-type;
a {
color: @ui-selected-type;
}
border-color: @ui-selected-border;
box-shadow: 0 0 2px 0 fade(@ui-selected-border, 80%);
&::before {
content: "";
position: absolute;
@@ -79,8 +73,10 @@
bottom: 0;
border: 2px solid fade(white, 80%);
}
&:hover {
color: @ui-selected-type-hover;
a {
color: @ui-selected-type-hover;
}
@@ -88,7 +84,6 @@
}
.umb-tree-item.current > .umb-tree-item__inner {
background: @ui-active;
color:@ui-active-type;
@@ -33,12 +33,6 @@
display: none;
}
}
//loader defaults
.umb-loader {
height: 10px;
margin: 10px 10px 10px 10px;
}
.search-subtitle {
color: @gray-7;
@@ -72,8 +66,7 @@ body.touch .umb-tree {
overflow: hidden;
display: flex;
flex-wrap: nowrap;
align-items: center;
align-items: center;
border:2px solid transparent;
color: @ui-option-type;
@@ -183,7 +176,6 @@ body.touch .umb-tree {
&:hover {
background: @btnBackgroundHighlight;
}
// NOTE - We're having to repeat ourselves here due to an .sr-only class appearing in umbraco/lib/font-awesome/css/font-awesome.min.css
&.sr-only--hoverable:hover,
&.sr-only--focusable:focus {
@@ -1,12 +1,14 @@
@boxUnit: 10px;
.umb-box {
background: @white;
border-radius: 3px;
margin-bottom: 20px;
margin-bottom: @boxUnit * 2;
box-shadow: 0 1px 1px 0 rgba(0,0,0,.16);
}
.umb-box-header {
padding: 10px 20px;
padding: @boxUnit @boxUnit * 2;
border-bottom: 1px solid @gray-9;
display: flex;
align-items: center;
@@ -27,5 +29,19 @@
}
.umb-box-content {
padding: 20px;
padding: @boxUnit * 2;
}
// allow side-by-side boxes
.umb-box-row {
margin-left:-@boxUnit;
margin-right:-@boxUnit;
display:flex;
justify-content: space-around;
.umb-box {
margin-left:@boxUnit;
margin-right:@boxUnit;
flex:1;
}
}
@@ -1,4 +1,4 @@
.flatpickr-calendar.flatpickr-calendar {
.flatpickr-calendar {
border-radius: @baseBorderRadius;
box-shadow: 0 5px 10px 0 rgba(0,0,0,0.16);
}
@@ -14,15 +14,18 @@
margin: 0 0 20px 0;
position: relative;
transition: height 0.8s;
.illustration {
width: 300px;
}
&.is-small {
height: 100px;
.illustration {
width: 200px;
}
}
&.drag-over {
border: 1px dashed @gray-1;
}
@@ -35,15 +38,19 @@
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
display: flex;
flex-direction: column;
}
// file select link
.file-select {
background: transparent;
border: 0;
padding: 0;
font-size: 15px;
color: @ui-action-discreet-type;
cursor: pointer;
margin-top: 10px;
&:hover {
@@ -0,0 +1,42 @@
// Loading Animation
// ------------------------
.umb-loader {
background-color: @blue;
margin-top: 0;
margin-left: -100%;
animation-name: bounce_loadingProgressG;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-timing-function: linear;
width: 100%;
height: 2px;
}
@keyframes bounce_loadingProgressG {
0% {
margin-left: -100%;
}
100% {
margin-left: 100%;
}
}
.umb-loader-wrapper {
position: absolute;
right: 0;
left: 0;
margin: 10px 0;
overflow: hidden;
}
.umb-loader-wrapper.-top {
top: 0;
bottom: auto;
}
.umb-loader-wrapper.-bottom {
top: auto;
bottom: 0;
}
@@ -264,12 +264,16 @@
flex-flow: row wrap;
}
a.umb-package-details__back-link {
.umb-package-details__back-action {
font-weight: bold;
color: @black;
padding: 0;
border: 0;
background-color: transparent;
}
.umb-package-details__back-link:hover {
.umb-package-details__back-action:focus,
.umb-package-details__back-action:hover {
color: @gray-4;
text-decoration: none;
}
@@ -295,7 +295,11 @@ input.umb-table__input {
.umb-table__row-expand {
font-size: 12px;
text-decoration: none;
color: @black;
color: @gray-4;
&:hover {
color: @black;
}
}
.umb-table__row-expand--hidden {
@@ -13,6 +13,9 @@
@import "../../lib/bootstrap/less/thumbnails.less";
@import "../../lib/bootstrap/less/media.less";
// Umbraco Components
@import "components/umb-loader.less";
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
@@ -165,45 +168,17 @@ input.ng-dirty.ng-invalid{border-color: #b94a48; color: #b94a48;}
opacity:1;
}
.umb-installer-loader {
margin: 0;
width: 0;
z-index: 777;
.umb-loader{
background-color: @white;
margin-top:0;
margin-left:-100%;
animation-name:bounce_loadingProgressG;
animation-duration:1s;
animation-iteration-count:infinite;
animation-timing-function:linear;
width:100%;
height: 5px;
}
@keyframes bounce_loadingProgressG{
0%{
margin-left:-100%;
}
100%{
margin-left:100%;
.umb-loader {
background-color: @white;
height: 5px;
}
}
//loader defaults
.umb-loader-container, .umb-loader-done{
height: 3px;
position: absolute;
bottom: 0;
left: 0;
overflow: hidden;
width: 0%;
z-index: 777;
}
.umb-loader-done{
right: 0%;
background: @white;
}
.permissions-report {
overflow:auto;
height:320px;
@@ -486,42 +486,6 @@ table thead a {
color:@green;
}
// Loading Animation
// ------------------------
.umb-loader{
background-color: @blue;
margin-top:0;
margin-left:-100%;
animation-name:bounce_loadingProgressG;
animation-duration:1s;
animation-iteration-count:infinite;
animation-timing-function:linear;
width:100%;
height:2px;
}
@keyframes bounce_loadingProgressG{
0%{
margin-left:-100%;
}
100%{
margin-left:100%;
}
}
.umb-loader-wrapper {
position: absolute;
right: 0;
left: 0;
margin: 10px 0;
overflow: hidden;
}
.umb-loader-wrapper.-bottom {
bottom: 0;
}
// Helpers
.strong {
@@ -592,13 +556,3 @@ input[type=checkbox]:checked + .input-label--small {
background-color: @green-l3;
text-decoration: none;
}
.visuallyhidden{
position: absolute !important;
clip: rect(1px, 1px, 1px, 1px);
padding:0 !important;
border:0 !important;
height: 1px !important;
width: 1px !important;
overflow: hidden;
}
@@ -5,7 +5,6 @@
// --------------------------------------------------
.umb-property-editor {
width: 100%;
position:relative;
}
.umb-property-editor-tiny {

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