Compare commits

...

19 Commits

Author SHA1 Message Date
Jasmin Savard
0ae9b4b4fe Adding fix for MenuPart write concurrency issue 2018-04-27 17:20:54 -04:00
Jasmin Savard
9446665c95 Fix write concurrency issue with LayoutPart 2018-04-23 00:42:19 -04:00
Jasmin Savard
df69a86d11 Fix MainMenu module write concurrency issue 2018-04-20 07:31:51 -04:00
Jasmin Savard
01481bad3c Fix Alias versions issue
Changed the lock position to be in PublishAlias(). That way it locks the execution of both the ProcessAlias() and PublishHomeAlias() so that both gets executed before moving to the next write.
2018-04-20 07:11:49 -04:00
Jasmin Savard
5133a3cf49 Adding LockProviding to AutoroutePartHandler 2018-04-20 06:37:42 -04:00
Jasmin Savard
a9b184a301 Do not ProcessPath if part.HasDraft()
Simply don't try to generate an Alias if the content item created is a draft.
2018-04-18 13:57:05 -04:00
Jasmin Savard
9e98c8f6e6 Merge branch 'issues/7708_Concurrency' into autoroute-concurrency 2018-04-15 14:56:56 -04:00
Jasmin Savard
6c48096e46 Fix PublishLaterPart concurrency issue 2018-04-15 00:52:51 -04:00
Jasmin Savard
a93fd9fee9 Fix concurrency issue with TagService 2018-04-14 05:36:34 -04:00
Jasmin Savard
0b1dee0c15 Removing GetHomePage() from HomeAliasService
Refactor methods using it by replacing it with GetHomePageId() that is now using AliasService to retrieve the Id from the RouteCollection values.
2018-04-13 12:18:33 -04:00
Jasmin Savard
d12554fbbc removing not needed using 2018-04-12 20:53:48 -04:00
Jasmin Savard
f410146e54 Using AliasService instead of retrieving ContentItem data from database. 2018-04-12 19:39:52 -04:00
matteo.piovanelli
b775031b6b Reverted AutorouteService.
Upgraded and fixed LockingProvider implementation. Updated ILockingProvider comments.
2018-04-11 17:07:09 +02:00
matteo.piovanelli
76e4fd5034 Added ILockingProvider and corresponding base implementation for methods to handle locking around critical code.
Modified AutorouteService and TagService to use the ILockingProvider, as those were found to be two places with unhandled critical code.
Updated TagsServiceTests. Update StubWorkContextAccessor to return the SiteName, since that is used in the updated TagService to generate a unique string for locking.
2018-04-09 14:31:08 +02:00
Matteo Piovanelli
6da6368bcc Removed LocalizationPart (#7911) 2018-04-05 12:24:34 -07:00
Sébastien Ros
e3f010d62b Remove usage of BinaryFormatter for file cache (#8005) 2018-03-29 14:38:54 -07:00
Sébastien Ros
1908fff595 Fixing the FileSystemOutputCache feature (#7913)
Fixes #8004 #6115 
- Caching keys for filenames to prevent too long paths
- Separating metadata from content storage to optimize some scenarios
2018-03-29 09:53:56 -07:00
greg84
cc5ffcd313 Allow event propagation for RemoveUrl/UnsafeUrl links (#7994) 2018-03-22 12:21:11 -07:00
Matteo Piovanelli
b0db0454a5 Fix/failed tests (#7993)
* Fixed 10 tests that failed because the constructor for a service had changed. 26 Failing tests to fix.

* Fixed 4 tests that failed because the constructor for a service had changed. 22 Failing tests to fix.

* Fixed 7 tests from Orchard.Specs that were failing because of the changes made to how the name property of the form components for fields were generated.

* Fixed 2 more tests from Orchard.Specs
2018-03-15 12:16:16 -07:00
40 changed files with 1105 additions and 362 deletions

View File

@@ -48,7 +48,7 @@ Scenario: Creating and using Boolean fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].BooleanFieldSettings.Hint | Check if the event is active |
| Fields[Active].BooleanFieldSettings.Hint | Check if the event is active |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "Check if the event is active"
@@ -57,7 +57,7 @@ Scenario: Creating and using Boolean fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].BooleanFieldSettings.DefaultValue | True |
| Fields[Active].BooleanFieldSettings.DefaultValue | True |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "checked=\"checked\""
@@ -66,7 +66,7 @@ Scenario: Creating and using Boolean fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].BooleanFieldSettings.Optional | false |
| Fields[Active].BooleanFieldSettings.Optional | false |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
And I fill in

View File

@@ -157,7 +157,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table4.AddRow(new string[] {
"Fields[0].BooleanFieldSettings.Hint",
"Fields[Active].BooleanFieldSettings.Hint",
"Check if the event is active"});
#line 49
testRunner.And("I fill in", ((string)(null)), table4, "And ");
@@ -174,7 +174,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table5.AddRow(new string[] {
"Fields[0].BooleanFieldSettings.DefaultValue",
"Fields[Active].BooleanFieldSettings.DefaultValue",
"True"});
#line 58
testRunner.And("I fill in", ((string)(null)), table5, "And ");
@@ -191,7 +191,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table6.AddRow(new string[] {
"Fields[0].BooleanFieldSettings.Optional",
"Fields[Active].BooleanFieldSettings.Optional",
"false"});
#line 67
testRunner.And("I fill in", ((string)(null)), table6, "And ");

View File

@@ -61,7 +61,7 @@ Scenario: Creating and using Date fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].DateTimeFieldSettings.Hint | Enter the date of the event |
| Fields[EventDate].DateTimeFieldSettings.Hint | Enter the date of the event |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "Enter the date of the event"
@@ -70,7 +70,7 @@ Scenario: Creating and using Date fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].DateTimeFieldSettings.Display | DateOnly |
| Fields[EventDate].DateTimeFieldSettings.Display | DateOnly |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "Event.EventDate.Editor.Date"
@@ -80,7 +80,7 @@ Scenario: Creating and using Date fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].DateTimeFieldSettings.Display | TimeOnly |
| Fields[EventDate].DateTimeFieldSettings.Display | TimeOnly |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "Event.EventDate.Editor.Time"
@@ -90,8 +90,8 @@ Scenario: Creating and using Date fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].DateTimeFieldSettings.Display | DateAndTime |
| Fields[0].DateTimeFieldSettings.Required | true |
| Fields[EventDate].DateTimeFieldSettings.Display | DateAndTime |
| Fields[EventDate].DateTimeFieldSettings.Required | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "Event.EventDate.Editor.Date"
@@ -119,8 +119,8 @@ Scenario: Creating and using Date fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].DateTimeFieldSettings.Display | DateOnly |
| Fields[0].DateTimeFieldSettings.Required | true |
| Fields[EventDate].DateTimeFieldSettings.Display | DateOnly |
| Fields[EventDate].DateTimeFieldSettings.Required | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "Event.EventDate.Editor.Date"
@@ -131,8 +131,8 @@ Scenario: Creating and using Date fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].DateTimeFieldSettings.Display | TimeOnly |
| Fields[0].DateTimeFieldSettings.Required | true |
| Fields[EventDate].DateTimeFieldSettings.Display | TimeOnly |
| Fields[EventDate].DateTimeFieldSettings.Required | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "Event.EventDate.Editor.Date"
@@ -143,9 +143,9 @@ Scenario: Creating and using Date fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].DateTimeFieldSettings.Display | DateAndTime |
| Fields[0].DateTimeFieldSettings.Editor.Date | 01/31/2016 |
| Fields[0].DateTimeFieldSettings.Editor.Time | 10:00 AM |
| Fields[EventDate].DateTimeFieldSettings.Display | DateAndTime |
| Fields[EventDate].DateTimeFieldSettings.Editor.Date | 01/31/2016 |
| Fields[EventDate].DateTimeFieldSettings.Editor.Time | 10:00 AM |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "Event.EventDate.Editor.Date"
@@ -190,8 +190,8 @@ Scenario: Creating and using date time fields in another culture
And I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].DateTimeFieldSettings.Display | DateAndTime |
| Fields[0].DateTimeFieldSettings.Required | true |
| Fields[EventDate].DateTimeFieldSettings.Display | DateAndTime |
| Fields[EventDate].DateTimeFieldSettings.Required | true |
And I hit "Save"
When I go to "Admin/Contents/Create/Event"
And I fill in

View File

@@ -186,7 +186,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table6.AddRow(new string[] {
"Fields[0].DateTimeFieldSettings.Hint",
"Fields[EventDate].DateTimeFieldSettings.Hint",
"Enter the date of the event"});
#line 62
testRunner.And("I fill in", ((string)(null)), table6, "And ");
@@ -203,7 +203,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table7.AddRow(new string[] {
"Fields[0].DateTimeFieldSettings.Display",
"Fields[EventDate].DateTimeFieldSettings.Display",
"DateOnly"});
#line 71
testRunner.And("I fill in", ((string)(null)), table7, "And ");
@@ -222,7 +222,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table8.AddRow(new string[] {
"Fields[0].DateTimeFieldSettings.Display",
"Fields[EventDate].DateTimeFieldSettings.Display",
"TimeOnly"});
#line 81
testRunner.And("I fill in", ((string)(null)), table8, "And ");
@@ -241,10 +241,10 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table9.AddRow(new string[] {
"Fields[0].DateTimeFieldSettings.Display",
"Fields[EventDate].DateTimeFieldSettings.Display",
"DateAndTime"});
table9.AddRow(new string[] {
"Fields[0].DateTimeFieldSettings.Required",
"Fields[EventDate].DateTimeFieldSettings.Required",
"true"});
#line 91
testRunner.And("I fill in", ((string)(null)), table9, "And ");
@@ -309,10 +309,10 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table13.AddRow(new string[] {
"Fields[0].DateTimeFieldSettings.Display",
"Fields[EventDate].DateTimeFieldSettings.Display",
"DateOnly"});
table13.AddRow(new string[] {
"Fields[0].DateTimeFieldSettings.Required",
"Fields[EventDate].DateTimeFieldSettings.Required",
"true"});
#line 120
testRunner.And("I fill in", ((string)(null)), table13, "And ");
@@ -333,10 +333,10 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table14.AddRow(new string[] {
"Fields[0].DateTimeFieldSettings.Display",
"Fields[EventDate].DateTimeFieldSettings.Display",
"TimeOnly"});
table14.AddRow(new string[] {
"Fields[0].DateTimeFieldSettings.Required",
"Fields[EventDate].DateTimeFieldSettings.Required",
"true"});
#line 132
testRunner.And("I fill in", ((string)(null)), table14, "And ");
@@ -357,13 +357,13 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table15.AddRow(new string[] {
"Fields[0].DateTimeFieldSettings.Display",
"Fields[EventDate].DateTimeFieldSettings.Display",
"DateAndTime"});
table15.AddRow(new string[] {
"Fields[0].DateTimeFieldSettings.Editor.Date",
"Fields[EventDate].DateTimeFieldSettings.Editor.Date",
"01/31/2016"});
table15.AddRow(new string[] {
"Fields[0].DateTimeFieldSettings.Editor.Time",
"Fields[EventDate].DateTimeFieldSettings.Editor.Time",
"10:00 AM"});
#line 144
testRunner.And("I fill in", ((string)(null)), table15, "And ");
@@ -461,10 +461,10 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table18.AddRow(new string[] {
"Fields[0].DateTimeFieldSettings.Display",
"Fields[EventDate].DateTimeFieldSettings.Display",
"DateAndTime"});
table18.AddRow(new string[] {
"Fields[0].DateTimeFieldSettings.Required",
"Fields[EventDate].DateTimeFieldSettings.Required",
"true"});
#line 191
testRunner.And("I fill in", ((string)(null)), table18, "And ");

View File

@@ -35,7 +35,7 @@ Scenario: Creating and using Enumeration fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].EnumerationFieldSettings.Options | Seattle |
| Fields[Location].EnumerationFieldSettings.Options | Seattle |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "<option>Seattle</option>"
@@ -57,7 +57,7 @@ Scenario: Creating and using Enumeration fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].EnumerationFieldSettings.Hint | Please select a location |
| Fields[Location].EnumerationFieldSettings.Hint | Please select a location |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "Please select a location"
@@ -66,7 +66,7 @@ Scenario: Creating and using Enumeration fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].EnumerationFieldSettings.ListMode | Dropdown |
| Fields[Location].EnumerationFieldSettings.ListMode | Dropdown |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "select id=\"Event_Location_Value\" name=\"Event.Location.Value\""
@@ -75,7 +75,7 @@ Scenario: Creating and using Enumeration fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].EnumerationFieldSettings.ListMode | Radiobutton |
| Fields[Location].EnumerationFieldSettings.ListMode | Radiobutton |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "input id=\"Event_Location_Value\" name=\"Event.Location.Value\" type=\"radio\""
@@ -84,7 +84,7 @@ Scenario: Creating and using Enumeration fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].EnumerationFieldSettings.ListMode | Listbox |
| Fields[Location].EnumerationFieldSettings.ListMode | Listbox |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "select id=\"Event_Location_SelectedValues\" multiple=\"multiple\" name=\"Event.Location.SelectedValues\""
@@ -93,7 +93,7 @@ Scenario: Creating and using Enumeration fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].EnumerationFieldSettings.ListMode | Checkbox |
| Fields[Location].EnumerationFieldSettings.ListMode | Checkbox |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "input type=\"checkbox\" name=\"Event.Location.SelectedValues\""
@@ -102,7 +102,7 @@ Scenario: Creating and using Enumeration fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].EnumerationFieldSettings.Required | true |
| Fields[Location].EnumerationFieldSettings.Required | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
And I hit "Save Draft"
@@ -112,9 +112,9 @@ Scenario: Creating and using Enumeration fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].EnumerationFieldSettings.Options | Seattle |
| Fields[0].EnumerationFieldSettings.ListMode | Dropdown |
| Fields[0].EnumerationFieldSettings.DefaultValue | Seattle |
| Fields[Location].EnumerationFieldSettings.Options | Seattle |
| Fields[Location].EnumerationFieldSettings.ListMode | Dropdown |
| Fields[Location].EnumerationFieldSettings.DefaultValue | Seattle |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "selected=\"selected">Seattle"
@@ -123,8 +123,8 @@ Scenario: Creating and using Enumeration fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].EnumerationFieldSettings.Required | true |
| Fields[0].EnumerationFieldSettings.ListMode | Listbox |
| Fields[Location].EnumerationFieldSettings.Required | true |
| Fields[Location].EnumerationFieldSettings.ListMode | Listbox |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "required=\"required\""
@@ -133,8 +133,8 @@ Scenario: Creating and using Enumeration fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].EnumerationFieldSettings.Required | false |
| Fields[0].EnumerationFieldSettings.ListMode | Listbox |
| Fields[Location].EnumerationFieldSettings.Required | false |
| Fields[Location].EnumerationFieldSettings.ListMode | Listbox |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should not see "required=\"required\""

View File

@@ -132,7 +132,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table3.AddRow(new string[] {
"Fields[0].EnumerationFieldSettings.Options",
"Fields[Location].EnumerationFieldSettings.Options",
"Seattle"});
#line 36
testRunner.And("I fill in", ((string)(null)), table3, "And ");
@@ -174,7 +174,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table5.AddRow(new string[] {
"Fields[0].EnumerationFieldSettings.Hint",
"Fields[Location].EnumerationFieldSettings.Hint",
"Please select a location"});
#line 58
testRunner.And("I fill in", ((string)(null)), table5, "And ");
@@ -191,7 +191,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table6.AddRow(new string[] {
"Fields[0].EnumerationFieldSettings.ListMode",
"Fields[Location].EnumerationFieldSettings.ListMode",
"Dropdown"});
#line 67
testRunner.And("I fill in", ((string)(null)), table6, "And ");
@@ -208,7 +208,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table7.AddRow(new string[] {
"Fields[0].EnumerationFieldSettings.ListMode",
"Fields[Location].EnumerationFieldSettings.ListMode",
"Radiobutton"});
#line 76
testRunner.And("I fill in", ((string)(null)), table7, "And ");
@@ -226,7 +226,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table8.AddRow(new string[] {
"Fields[0].EnumerationFieldSettings.ListMode",
"Fields[Location].EnumerationFieldSettings.ListMode",
"Listbox"});
#line 85
testRunner.And("I fill in", ((string)(null)), table8, "And ");
@@ -244,7 +244,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table9.AddRow(new string[] {
"Fields[0].EnumerationFieldSettings.ListMode",
"Fields[Location].EnumerationFieldSettings.ListMode",
"Checkbox"});
#line 94
testRunner.And("I fill in", ((string)(null)), table9, "And ");
@@ -261,7 +261,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table10.AddRow(new string[] {
"Fields[0].EnumerationFieldSettings.Required",
"Fields[Location].EnumerationFieldSettings.Required",
"true"});
#line 103
testRunner.And("I fill in", ((string)(null)), table10, "And ");
@@ -280,13 +280,13 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table11.AddRow(new string[] {
"Fields[0].EnumerationFieldSettings.Options",
"Fields[Location].EnumerationFieldSettings.Options",
"Seattle"});
table11.AddRow(new string[] {
"Fields[0].EnumerationFieldSettings.ListMode",
"Fields[Location].EnumerationFieldSettings.ListMode",
"Dropdown"});
table11.AddRow(new string[] {
"Fields[0].EnumerationFieldSettings.DefaultValue",
"Fields[Location].EnumerationFieldSettings.DefaultValue",
"Seattle"});
#line 113
testRunner.And("I fill in", ((string)(null)), table11, "And ");
@@ -303,10 +303,10 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table12.AddRow(new string[] {
"Fields[0].EnumerationFieldSettings.Required",
"Fields[Location].EnumerationFieldSettings.Required",
"true"});
table12.AddRow(new string[] {
"Fields[0].EnumerationFieldSettings.ListMode",
"Fields[Location].EnumerationFieldSettings.ListMode",
"Listbox"});
#line 124
testRunner.And("I fill in", ((string)(null)), table12, "And ");
@@ -323,10 +323,10 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table13.AddRow(new string[] {
"Fields[0].EnumerationFieldSettings.Required",
"Fields[Location].EnumerationFieldSettings.Required",
"false"});
table13.AddRow(new string[] {
"Fields[0].EnumerationFieldSettings.ListMode",
"Fields[Location].EnumerationFieldSettings.ListMode",
"Listbox"});
#line 134
testRunner.And("I fill in", ((string)(null)), table13, "And ");

View File

@@ -35,7 +35,7 @@ Scenario: Creating and using Input fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].InputFieldSettings.Hint | Enter the contact email address |
| Fields[Contact].InputFieldSettings.Hint | Enter the contact email address |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "Enter the contact email address"
@@ -44,7 +44,7 @@ Scenario: Creating and using Input fields
#When I go to "Admin/ContentTypes/Edit/Event"
# And I fill in
# | name | value |
# | Fields[0].InputFieldSettings.Pattern | [^@]*@[^@]* |
# | Fields[Contact].InputFieldSettings.Pattern | [^@]*@[^@]* |
# And I hit "Save"
# And I go to "Admin/Contents/Create/Event"
#Then I should see "pattern=\"[^@]*@[^@]*\""
@@ -53,7 +53,7 @@ Scenario: Creating and using Input fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].InputFieldSettings.Type | Email |
| Fields[Contact].InputFieldSettings.Type | Email |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "type=\"email\""
@@ -62,7 +62,7 @@ Scenario: Creating and using Input fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].InputFieldSettings.Title | Enter an email address |
| Fields[Contact].InputFieldSettings.Title | Enter an email address |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "title=\"Enter an email address\""
@@ -71,7 +71,7 @@ Scenario: Creating and using Input fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].InputFieldSettings.AutoFocus | true |
| Fields[Contact].InputFieldSettings.AutoFocus | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "autofocus=\"autofocus\""
@@ -80,7 +80,7 @@ Scenario: Creating and using Input fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].InputFieldSettings.AutoComplete | true |
| Fields[Contact].InputFieldSettings.AutoComplete | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "autocomplete=\"on\""
@@ -89,7 +89,7 @@ Scenario: Creating and using Input fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].InputFieldSettings.Placeholder | email@domain.com |
| Fields[Contact].InputFieldSettings.Placeholder | email@domain.com |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "placeholder=\"email@domain.com\""
@@ -98,7 +98,7 @@ Scenario: Creating and using Input fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].InputFieldSettings.MaxLength | 100 |
| Fields[Contact].InputFieldSettings.MaxLength | 100 |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "maxlength=\"100\""
@@ -107,7 +107,7 @@ Scenario: Creating and using Input fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].InputFieldSettings.Required | true |
| Fields[Contact].InputFieldSettings.Required | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
And I fill in
@@ -133,7 +133,7 @@ Scenario: Creating and using Input fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].InputFieldSettings.DefaultValue | contact@orchardproject.net |
| Fields[Contact].InputFieldSettings.DefaultValue | contact@orchardproject.net |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "value=\"contact@orchardproject.net\""
@@ -142,7 +142,7 @@ Scenario: Creating and using Input fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].InputFieldSettings.Required | true |
| Fields[Contact].InputFieldSettings.Required | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "required=\"required\""
@@ -151,7 +151,7 @@ Scenario: Creating and using Input fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].InputFieldSettings.Required | false |
| Fields[Contact].InputFieldSettings.Required | false |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should not see "required=\"required\""

View File

@@ -132,7 +132,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table3.AddRow(new string[] {
"Fields[0].InputFieldSettings.Hint",
"Fields[Contact].InputFieldSettings.Hint",
"Enter the contact email address"});
#line 36
testRunner.And("I fill in", ((string)(null)), table3, "And ");
@@ -149,7 +149,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table4.AddRow(new string[] {
"Fields[0].InputFieldSettings.Type",
"Fields[Contact].InputFieldSettings.Type",
"Email"});
#line 54
testRunner.And("I fill in", ((string)(null)), table4, "And ");
@@ -166,7 +166,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table5.AddRow(new string[] {
"Fields[0].InputFieldSettings.Title",
"Fields[Contact].InputFieldSettings.Title",
"Enter an email address"});
#line 63
testRunner.And("I fill in", ((string)(null)), table5, "And ");
@@ -183,7 +183,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table6.AddRow(new string[] {
"Fields[0].InputFieldSettings.AutoFocus",
"Fields[Contact].InputFieldSettings.AutoFocus",
"true"});
#line 72
testRunner.And("I fill in", ((string)(null)), table6, "And ");
@@ -200,7 +200,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table7.AddRow(new string[] {
"Fields[0].InputFieldSettings.AutoComplete",
"Fields[Contact].InputFieldSettings.AutoComplete",
"true"});
#line 81
testRunner.And("I fill in", ((string)(null)), table7, "And ");
@@ -217,7 +217,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table8.AddRow(new string[] {
"Fields[0].InputFieldSettings.Placeholder",
"Fields[Contact].InputFieldSettings.Placeholder",
"email@domain.com"});
#line 90
testRunner.And("I fill in", ((string)(null)), table8, "And ");
@@ -234,7 +234,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table9.AddRow(new string[] {
"Fields[0].InputFieldSettings.MaxLength",
"Fields[Contact].InputFieldSettings.MaxLength",
"100"});
#line 99
testRunner.And("I fill in", ((string)(null)), table9, "And ");
@@ -251,7 +251,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table10.AddRow(new string[] {
"Fields[0].InputFieldSettings.Required",
"Fields[Contact].InputFieldSettings.Required",
"true"});
#line 108
testRunner.And("I fill in", ((string)(null)), table10, "And ");
@@ -304,7 +304,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table13.AddRow(new string[] {
"Fields[0].InputFieldSettings.DefaultValue",
"Fields[Contact].InputFieldSettings.DefaultValue",
"contact@orchardproject.net"});
#line 134
testRunner.And("I fill in", ((string)(null)), table13, "And ");
@@ -321,7 +321,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table14.AddRow(new string[] {
"Fields[0].InputFieldSettings.Required",
"Fields[Contact].InputFieldSettings.Required",
"true"});
#line 143
testRunner.And("I fill in", ((string)(null)), table14, "And ");
@@ -338,7 +338,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table15.AddRow(new string[] {
"Fields[0].InputFieldSettings.Required",
"Fields[Contact].InputFieldSettings.Required",
"false"});
#line 152
testRunner.And("I fill in", ((string)(null)), table15, "And ");

View File

@@ -51,7 +51,7 @@ Scenario: Creating and using Link fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].LinkFieldSettings.Hint | Enter the url of the web site |
| Fields[SiteUrl].LinkFieldSettings.Hint | Enter the url of the web site |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "Enter the url of the web site"
@@ -60,7 +60,7 @@ Scenario: Creating and using Link fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].LinkFieldSettings.Required | true |
| Fields[SiteUrl].LinkFieldSettings.Required | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
And I fill in
@@ -73,7 +73,7 @@ Scenario: Creating and using Link fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].LinkFieldSettings.DefaultValue | http://www.orchardproject.net |
| Fields[SiteUrl].LinkFieldSettings.DefaultValue | http://www.orchardproject.net |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "value=\"http://www.orchardproject.net\""
@@ -82,7 +82,7 @@ Scenario: Creating and using Link fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].LinkFieldSettings.Required | true |
| Fields[SiteUrl].LinkFieldSettings.Required | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "required=\"required\""
@@ -91,7 +91,7 @@ Scenario: Creating and using Link fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].LinkFieldSettings.Required | false |
| Fields[SiteUrl].LinkFieldSettings.Required | false |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should not see "required=\"required\""

View File

@@ -166,7 +166,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table5.AddRow(new string[] {
"Fields[0].LinkFieldSettings.Hint",
"Fields[SiteUrl].LinkFieldSettings.Hint",
"Enter the url of the web site"});
#line 52
testRunner.And("I fill in", ((string)(null)), table5, "And ");
@@ -183,7 +183,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table6.AddRow(new string[] {
"Fields[0].LinkFieldSettings.Required",
"Fields[SiteUrl].LinkFieldSettings.Required",
"true"});
#line 61
testRunner.And("I fill in", ((string)(null)), table6, "And ");
@@ -211,7 +211,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table8.AddRow(new string[] {
"Fields[0].LinkFieldSettings.DefaultValue",
"Fields[SiteUrl].LinkFieldSettings.DefaultValue",
"http://www.orchardproject.net"});
#line 74
testRunner.And("I fill in", ((string)(null)), table8, "And ");
@@ -228,7 +228,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table9.AddRow(new string[] {
"Fields[0].LinkFieldSettings.Required",
"Fields[SiteUrl].LinkFieldSettings.Required",
"true"});
#line 83
testRunner.And("I fill in", ((string)(null)), table9, "And ");
@@ -245,7 +245,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table10.AddRow(new string[] {
"Fields[0].LinkFieldSettings.Required",
"Fields[SiteUrl].LinkFieldSettings.Required",
"false"});
#line 92
testRunner.And("I fill in", ((string)(null)), table10, "And ");

View File

@@ -47,7 +47,7 @@ Scenario: Creating and using media fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].MediaPickerFieldSettings.Hint | Please select a file |
| Fields[File].MediaPickerFieldSettings.Hint | Please select a file |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "Please select a file"
@@ -56,7 +56,7 @@ Scenario: Creating and using media fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].MediaPickerFieldSettings.Required | true |
| Fields[File].MediaPickerFieldSettings.Required | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
And I fill in
@@ -69,8 +69,8 @@ Scenario: Creating and using media fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| ext-Fields[0].MediaPickerFieldSettings | true |
| Fields[0].MediaPickerFieldSettings.AllowedExtensions | jpg |
| ext-Fields[File].MediaPickerFieldSettings | true |
| Fields[File].MediaPickerFieldSettings.AllowedExtensions | jpg |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
And I fill in

View File

@@ -153,7 +153,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table4.AddRow(new string[] {
"Fields[0].MediaPickerFieldSettings.Hint",
"Fields[File].MediaPickerFieldSettings.Hint",
"Please select a file"});
#line 48
testRunner.And("I fill in", ((string)(null)), table4, "And ");
@@ -170,7 +170,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table5.AddRow(new string[] {
"Fields[0].MediaPickerFieldSettings.Required",
"Fields[File].MediaPickerFieldSettings.Required",
"true"});
#line 57
testRunner.And("I fill in", ((string)(null)), table5, "And ");
@@ -198,10 +198,10 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table7.AddRow(new string[] {
"ext-Fields[0].MediaPickerFieldSettings",
"ext-Fields[File].MediaPickerFieldSettings",
"true"});
table7.AddRow(new string[] {
"Fields[0].MediaPickerFieldSettings.AllowedExtensions",
"Fields[File].MediaPickerFieldSettings.AllowedExtensions",
"jpg"});
#line 70
testRunner.And("I fill in", ((string)(null)), table7, "And ");

View File

@@ -48,7 +48,7 @@ Scenario: Creating and using numeric fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].NumericFieldSettings.Hint | Please enter a number |
| Fields[Guests].NumericFieldSettings.Hint | Please enter a number |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "Please enter a number"
@@ -57,7 +57,7 @@ Scenario: Creating and using numeric fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].NumericFieldSettings.Required | true |
| Fields[Guests].NumericFieldSettings.Required | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
And I fill in
@@ -70,8 +70,8 @@ Scenario: Creating and using numeric fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].NumericFieldSettings.Minimum | -10 |
| Fields[0].NumericFieldSettings.Maximum | 100 |
| Fields[Guests].NumericFieldSettings.Minimum | -10 |
| Fields[Guests].NumericFieldSettings.Maximum | 100 |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "min=\"-10\""
@@ -92,9 +92,9 @@ Scenario: Creating and using numeric fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].NumericFieldSettings.Minimum | a |
| Fields[0].NumericFieldSettings.Maximum | b |
And I hit "Save Draft"
| Fields[Guests].NumericFieldSettings.Minimum | a |
| Fields[Guests].NumericFieldSettings.Maximum | b |
And I hit "Save"
Then I should see "The value &#39;a&#39; is not valid for Minimum."
And I should see "The value &#39;b&#39; is not valid for Maximum."
@@ -110,7 +110,7 @@ Scenario: Creating and using numeric fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].NumericFieldSettings.DefaultValue | 1234 |
| Fields[Guests].NumericFieldSettings.DefaultValue | 1234 |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "value=\"1234\""
@@ -119,7 +119,7 @@ Scenario: Creating and using numeric fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].NumericFieldSettings.Required | true |
| Fields[Guests].NumericFieldSettings.Required | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "required=\"required\""
@@ -128,7 +128,7 @@ Scenario: Creating and using numeric fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].NumericFieldSettings.Required | false |
| Fields[Guests].NumericFieldSettings.Required | false |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should not see "required=\"required\""

View File

@@ -157,7 +157,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table4.AddRow(new string[] {
"Fields[0].NumericFieldSettings.Hint",
"Fields[Guests].NumericFieldSettings.Hint",
"Please enter a number"});
#line 49
testRunner.And("I fill in", ((string)(null)), table4, "And ");
@@ -174,7 +174,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table5.AddRow(new string[] {
"Fields[0].NumericFieldSettings.Required",
"Fields[Guests].NumericFieldSettings.Required",
"true"});
#line 58
testRunner.And("I fill in", ((string)(null)), table5, "And ");
@@ -202,10 +202,10 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table7.AddRow(new string[] {
"Fields[0].NumericFieldSettings.Minimum",
"Fields[Guests].NumericFieldSettings.Minimum",
"-10"});
table7.AddRow(new string[] {
"Fields[0].NumericFieldSettings.Maximum",
"Fields[Guests].NumericFieldSettings.Maximum",
"100"});
#line 71
testRunner.And("I fill in", ((string)(null)), table7, "And ");
@@ -252,10 +252,10 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table10.AddRow(new string[] {
"Fields[0].NumericFieldSettings.Minimum",
"Fields[Guests].NumericFieldSettings.Minimum",
"a"});
table10.AddRow(new string[] {
"Fields[0].NumericFieldSettings.Maximum",
"Fields[Guests].NumericFieldSettings.Maximum",
"b"});
#line 93
testRunner.And("I fill in", ((string)(null)), table10, "And ");
@@ -287,7 +287,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table12.AddRow(new string[] {
"Fields[0].NumericFieldSettings.DefaultValue",
"Fields[Guests].NumericFieldSettings.DefaultValue",
"1234"});
#line 111
testRunner.And("I fill in", ((string)(null)), table12, "And ");
@@ -304,7 +304,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table13.AddRow(new string[] {
"Fields[0].NumericFieldSettings.Required",
"Fields[Guests].NumericFieldSettings.Required",
"true"});
#line 120
testRunner.And("I fill in", ((string)(null)), table13, "And ");
@@ -321,7 +321,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table14.AddRow(new string[] {
"Fields[0].NumericFieldSettings.Required",
"Fields[Guests].NumericFieldSettings.Required",
"false"});
#line 129
testRunner.And("I fill in", ((string)(null)), table14, "And ");

View File

@@ -35,7 +35,7 @@ Scenario: Creating and using Text fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].TextFieldSettingsEventsViewModel.Settings.Flavor | Large |
| Fields[Subject].TextFieldSettingsEventsViewModel.Settings.Flavor | Large |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "class=\"text large\""
@@ -44,7 +44,7 @@ Scenario: Creating and using Text fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].TextFieldSettingsEventsViewModel.Settings.Required | true |
| Fields[Subject].TextFieldSettingsEventsViewModel.Settings.Required | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
And I fill in
@@ -57,7 +57,7 @@ Scenario: Creating and using Text fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].TextFieldSettingsEventsViewModel.Settings.Hint | Subject of the event |
| Fields[Subject].TextFieldSettingsEventsViewModel.Settings.Hint | Subject of the event |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "Subject of the event"
@@ -77,7 +77,7 @@ Scenario: Creating and using Text fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].TextFieldSettingsEventsViewModel.Settings.DefaultValue | Orchard Harvest 2016 |
| Fields[Subject].TextFieldSettingsEventsViewModel.Settings.DefaultValue | Orchard Harvest 2016 |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "value=\"Orchard Harvest 2016\""
@@ -86,7 +86,7 @@ Scenario: Creating and using Text fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].TextFieldSettingsEventsViewModel.Settings.Required | true |
| Fields[Subject].TextFieldSettingsEventsViewModel.Settings.Required | true |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should see "required=\"required\""
@@ -95,7 +95,7 @@ Scenario: Creating and using Text fields
When I go to "Admin/ContentTypes/Edit/Event"
And I fill in
| name | value |
| Fields[0].TextFieldSettingsEventsViewModel.Settings.Required | false |
| Fields[Subject].TextFieldSettingsEventsViewModel.Settings.Required | false |
And I hit "Save"
And I go to "Admin/Contents/Create/Event"
Then I should not see "required=\"required\""

View File

@@ -132,7 +132,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table3.AddRow(new string[] {
"Fields[0].TextFieldSettingsEventsViewModel.Settings.Flavor",
"Fields[Subject].TextFieldSettingsEventsViewModel.Settings.Flavor",
"Large"});
#line 36
testRunner.And("I fill in", ((string)(null)), table3, "And ");
@@ -149,7 +149,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table4.AddRow(new string[] {
"Fields[0].TextFieldSettingsEventsViewModel.Settings.Required",
"Fields[Subject].TextFieldSettingsEventsViewModel.Settings.Required",
"true"});
#line 45
testRunner.And("I fill in", ((string)(null)), table4, "And ");
@@ -177,7 +177,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table6.AddRow(new string[] {
"Fields[0].TextFieldSettingsEventsViewModel.Settings.Hint",
"Fields[Subject].TextFieldSettingsEventsViewModel.Settings.Hint",
"Subject of the event"});
#line 58
testRunner.And("I fill in", ((string)(null)), table6, "And ");
@@ -215,7 +215,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table8.AddRow(new string[] {
"Fields[0].TextFieldSettingsEventsViewModel.Settings.DefaultValue",
"Fields[Subject].TextFieldSettingsEventsViewModel.Settings.DefaultValue",
"Orchard Harvest 2016"});
#line 78
testRunner.And("I fill in", ((string)(null)), table8, "And ");
@@ -232,7 +232,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table9.AddRow(new string[] {
"Fields[0].TextFieldSettingsEventsViewModel.Settings.Required",
"Fields[Subject].TextFieldSettingsEventsViewModel.Settings.Required",
"true"});
#line 87
testRunner.And("I fill in", ((string)(null)), table9, "And ");
@@ -249,7 +249,7 @@ this.ScenarioSetup(scenarioInfo);
"name",
"value"});
table10.AddRow(new string[] {
"Fields[0].TextFieldSettingsEventsViewModel.Settings.Required",
"Fields[Subject].TextFieldSettingsEventsViewModel.Settings.Required",
"false"});
#line 96
testRunner.And("I fill in", ((string)(null)), table10, "And ");

View File

@@ -11,6 +11,7 @@ using Orchard.ContentManagement.Handlers;
using Orchard.ContentManagement.Records;
using Orchard.Data;
using Orchard.Environment;
using Orchard.Locking;
using Orchard.Security;
using Orchard.Tags.Handlers;
using Orchard.Tags.Models;
@@ -40,6 +41,7 @@ namespace Orchard.Tests.Modules.Tags.Services {
builder.RegisterType<DefaultContentManager>().As<IContentManager>();
builder.RegisterType<StubCacheManager>().As<ICacheManager>();
builder.RegisterType<Signals>().As<ISignals>();
builder.RegisterType<LockingProvider>().As<ILockingProvider>();
builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>));
}

View File

@@ -45,7 +45,7 @@ namespace Orchard.Tests.Stubs {
}
public string SiteName {
get { throw new NotImplementedException(); }
get { return "TestSite"; }
}
public string SiteSalt {

View File

@@ -1,30 +1,38 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.Core.Navigation.Models;
using Orchard.Core.Navigation.Services;
using Orchard.Core.Navigation.ViewModels;
using Orchard.Localization;
using Orchard.Locking;
using Orchard.Security;
using Orchard.UI.Navigation;
using Orchard.Utility;
namespace Orchard.Core.Navigation.Drivers {
public class MenuPartDriver : ContentPartDriver<MenuPart> {
namespace Orchard.Core.Navigation.Drivers
{
public class MenuPartDriver : ContentPartDriver<MenuPart>
{
private readonly IAuthorizationService _authorizationService;
private readonly INavigationManager _navigationManager;
private readonly IOrchardServices _orchardServices;
private readonly IMenuService _menuService;
private readonly ILockingProvider _lockingProvider;
public MenuPartDriver(
IAuthorizationService authorizationService,
INavigationManager navigationManager,
IAuthorizationService authorizationService,
INavigationManager navigationManager,
IOrchardServices orchardServices,
IMenuService menuService) {
IMenuService menuService,
ILockingProvider lockingProvider)
{
_authorizationService = authorizationService;
_navigationManager = navigationManager;
_orchardServices = orchardServices;
_menuService = menuService;
_lockingProvider = lockingProvider;
T = NullLocalizer.Instance;
}
@@ -36,14 +44,28 @@ namespace Orchard.Core.Navigation.Drivers {
}
}
protected override DriverResult Editor(MenuPart part, dynamic shapeHelper) {
var allowedMenus = _menuService.GetMenus().Where(menu => _authorizationService.TryCheckAccess(Permissions.ManageMenus, _orchardServices.WorkContext.CurrentUser, menu)).ToList();
protected override DriverResult Editor(MenuPart part, dynamic shapeHelper)
{
var lockString = string.Join(".",
_orchardServices.WorkContext?.CurrentSite?.BaseUrl ?? "",
_orchardServices.WorkContext?.CurrentSite?.SiteName ?? "",
"MenuPartDriver");
IEnumerable<ContentItem> allowedMenus = null;
_lockingProvider.Lock(lockString,
() =>
{
allowedMenus = _menuService.GetMenus().Where(menu => _authorizationService.TryCheckAccess(Permissions.ManageMenus, _orchardServices.WorkContext.CurrentUser, menu)).ToList();
});
if (!allowedMenus.Any())
return null;
return ContentShape("Parts_Navigation_Menu_Edit", () => {
var model = new MenuPartViewModel {
return ContentShape("Parts_Navigation_Menu_Edit", () =>
{
var model = new MenuPartViewModel
{
CurrentMenuId = part.Menu == null ? -1 : part.Menu.Id,
ContentItem = part.ContentItem,
Menus = allowedMenus,
@@ -55,10 +77,12 @@ namespace Orchard.Core.Navigation.Drivers {
});
}
protected override DriverResult Editor(MenuPart part, IUpdateModel updater, dynamic shapeHelper) {
protected override DriverResult Editor(MenuPart part, IUpdateModel updater, dynamic shapeHelper)
{
var model = new MenuPartViewModel();
if(updater.TryUpdateModel(model, Prefix, null, null)) {
if (updater.TryUpdateModel(model, Prefix, null, null))
{
var menu = model.OnMenu ? _orchardServices.ContentManager.Get(model.CurrentMenuId) : null;
if (!_authorizationService.TryCheckAccess(Permissions.ManageMenus, _orchardServices.WorkContext.CurrentUser, menu))
@@ -67,10 +91,12 @@ namespace Orchard.Core.Navigation.Drivers {
part.MenuText = model.MenuText;
part.Menu = menu;
if (string.IsNullOrEmpty(part.MenuPosition) && menu != null) {
if (string.IsNullOrEmpty(part.MenuPosition) && menu != null)
{
part.MenuPosition = Position.GetNext(_navigationManager.BuildMenu(menu));
if (string.IsNullOrEmpty(part.MenuText)) {
if (string.IsNullOrEmpty(part.MenuText))
{
updater.AddModelError("MenuText", T("The MenuText field is required"));
}
}
@@ -79,9 +105,11 @@ namespace Orchard.Core.Navigation.Drivers {
return Editor(part, shapeHelper);
}
protected override void Importing(MenuPart part, ContentManagement.Handlers.ImportContentContext context) {
protected override void Importing(MenuPart part, ContentManagement.Handlers.ImportContentContext context)
{
// Don't do anything if the tag is not specified.
if (context.Data.Element(part.PartDefinition.Name) == null) {
if (context.Data.Element(part.PartDefinition.Name) == null)
{
return;
}
@@ -93,17 +121,21 @@ namespace Orchard.Core.Navigation.Drivers {
part.MenuPosition = position
);
context.ImportAttribute(part.PartDefinition.Name, "Menu", menuIdentity => {
context.ImportAttribute(part.PartDefinition.Name, "Menu", menuIdentity =>
{
var menu = context.GetItemFromSession(menuIdentity);
if (menu != null) {
if (menu != null)
{
part.Menu = menu;
}
});
}
protected override void Exporting(MenuPart part, ContentManagement.Handlers.ExportContentContext context) {
protected override void Exporting(MenuPart part, ContentManagement.Handlers.ExportContentContext context)
{
// is it on a menu ?
if(part.Menu == null) {
if (part.Menu == null)
{
return;
}

View File

@@ -1,31 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.Caching;
using Orchard.ContentManagement;
using Orchard.Core.Navigation.Models;
using Orchard.Core.Title.Models;
namespace Orchard.Core.Navigation.Services {
public class MainMenuService : IMenuService {
namespace Orchard.Core.Navigation.Services
{
public class MainMenuService : IMenuService
{
private readonly IContentManager _contentManager;
private ICacheManager _cacheManager;
private ISignals _signals;
public MainMenuService(IContentManager contentManager) {
public MainMenuService(IContentManager contentManager,
ICacheManager cacheManager,
ISignals signals)
{
_contentManager = contentManager;
_cacheManager = cacheManager;
_signals = signals;
}
public IEnumerable<MenuPart> Get() {
public IEnumerable<MenuPart> Get()
{
return _contentManager.Query<MenuPart, MenuPartRecord>().List();
}
public IEnumerable<MenuPart> GetMenuParts(int menuId) {
public IEnumerable<MenuPart> GetMenuParts(int menuId)
{
return _contentManager
.Query<MenuPart, MenuPartRecord>()
.Where( x => x.MenuId == menuId)
.Where(x => x.MenuId == menuId)
.List();
}
public IContent GetMenu(string menuName) {
if(string.IsNullOrWhiteSpace(menuName)) {
public IContent GetMenu(string menuName)
{
if (string.IsNullOrWhiteSpace(menuName))
{
return null;
}
@@ -36,32 +50,64 @@ namespace Orchard.Core.Navigation.Services {
.FirstOrDefault();
}
public IContent GetMenu(int menuId) {
return _contentManager.Get(menuId, VersionOptions.Published);
public IContent GetMenu(int menuId)
{
return _contentManager.Get(menuId, VersionOptions.Published);
}
public MenuPart Get(int menuPartId) {
public MenuPart Get(int menuPartId)
{
return _contentManager.Get<MenuPart>(menuPartId);
}
public IContent Create(string name) {
if(string.IsNullOrWhiteSpace(name)) {
public IContent Create(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(name);
}
var menu = _contentManager.Create("Menu");
menu.As<TitlePart>().Title = name;
_signals.Trigger("MainMenuService.AllMenus");
return menu;
}
public void Delete(MenuPart menuPart) {
public void Delete(MenuPart menuPart)
{
_contentManager.Remove(menuPart.ContentItem);
}
public IEnumerable<ContentItem> GetMenus() {
return _contentManager.Query().ForType("Menu").Join<TitlePartRecord>().OrderBy(x => x.Title).List();
private IEnumerable<ContentItem> AllMenus {
get {
var fromCache = FromCache();
if (fromCache.Item1.AddMinutes(2) < DateTime.Now)
{
// give 2 minutes lifetime to cache
_signals.Trigger("MainMenuService.AllMenus");
fromCache = FromCache();
}
return fromCache.Item2;
}
}
private Tuple<DateTime, IEnumerable<ContentItem>> FromCache()
{
return _cacheManager
.Get<string, Tuple<DateTime, IEnumerable<ContentItem>>>("MainMenuService.AllMenus", true, ctx =>
{
ctx.Monitor(_signals.When("MainMenuService.AllMenus"));
return Tuple.Create(DateTime.Now,
_contentManager.Query().ForType("Menu").Join<TitlePartRecord>().OrderBy(x => x.Title).List());
});
}
public IEnumerable<ContentItem> GetMenus()
{
return AllMenus; // _contentManager.Query().ForType("Menu").Join<TitlePartRecord>().OrderBy(x => x.Title).List();
}
}
}

View File

@@ -115,7 +115,7 @@ namespace Orchard.Autoroute.Drivers {
};
// Retrieve home page.
var homePageId = _homeAliasService.GetHomePageId(VersionOptions.Latest);
var homePageId = _homeAliasService.GetHomePageId();
var isHomePage = part.Id == homePageId;
viewModel.IsHomePage = isHomePage;

View File

@@ -5,33 +5,42 @@ using Orchard.ContentManagement;
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
using Orchard.Localization;
using Orchard.Locking;
using Orchard.UI.Notify;
namespace Orchard.Autoroute.Handlers {
public class AutoroutePartHandler : ContentHandler {
namespace Orchard.Autoroute.Handlers
{
public class AutoroutePartHandler : ContentHandler
{
private readonly Lazy<IAutorouteService> _autorouteService;
private readonly IOrchardServices _orchardServices;
private readonly IHomeAliasService _homeAliasService;
private readonly ILockingProvider _lockingProvider;
public Localizer T { get; set; }
public AutoroutePartHandler(
IRepository<AutoroutePartRecord> autoroutePartRepository,
Lazy<IAutorouteService> autorouteService,
IOrchardServices orchardServices,
IHomeAliasService homeAliasService) {
IOrchardServices orchardServices,
IHomeAliasService homeAliasService,
ILockingProvider lockingProvider)
{
Filters.Add(StorageFilter.For(autoroutePartRepository));
_autorouteService = autorouteService;
_orchardServices = orchardServices;
_homeAliasService = homeAliasService;
_lockingProvider = lockingProvider;
OnUpdated<AutoroutePart>((ctx, part) => CreateAlias(part));
OnCreated<AutoroutePart>((ctx, part) => {
OnCreated<AutoroutePart>((ctx, part) =>
{
// non-draftable items
if (part.ContentItem.VersionRecord == null) {
if (part.ContentItem.VersionRecord == null)
{
PublishAlias(part);
}
});
@@ -44,69 +53,109 @@ namespace Orchard.Autoroute.Handlers {
OnUnpublished<AutoroutePart>((ctx, part) => RemoveAlias(part));
// Register alias as identity
OnGetContentItemMetadata<AutoroutePart>((ctx, part) => {
OnGetContentItemMetadata<AutoroutePart>((ctx, part) =>
{
if (part.DisplayAlias != null)
ctx.Metadata.Identity.Add("alias", part.DisplayAlias);
});
}
private void CreateAlias(AutoroutePart part) {
private void CreateAlias(AutoroutePart part)
{
ProcessAlias(part);
}
private void PublishAlias(AutoroutePart part) {
ProcessAlias(part);
private string LockString {
get {
return string.Join(".",
_orchardServices.WorkContext?.CurrentSite?.BaseUrl ?? "",
_orchardServices.WorkContext?.CurrentSite?.SiteName ?? "",
"AutoroutePartHandler");
}
}
// Should it become the home page?
if (part.PromoteToHomePage) {
// Get the current homepage an unmark it as the homepage.
var currentHomePage = _homeAliasService.GetHomePage(VersionOptions.Latest);
if(currentHomePage != null && currentHomePage.Id != part.Id) {
var autoroutePart = currentHomePage.As<AutoroutePart>();
private void PublishAlias(AutoroutePart part)
{
// Lock this portion of the code to prevent race conditions where we are reading the aliases to compute
// stuff for a content, while another content is publishing.
// If the generated alias is empty, compute a new one.
_lockingProvider.Lock(LockString,
() =>
{
ProcessAlias(part);
if (autoroutePart != null) {
autoroutePart.PromoteToHomePage = false;
if(autoroutePart.IsPublished())
_orchardServices.ContentManager.Publish(autoroutePart.ContentItem);
// Should it become the home page?
if (part.PromoteToHomePage)
{
// Get the current homepage an unmark it as the homepage.
var currentHomePageId = _homeAliasService.GetHomePageId();
if (currentHomePageId != part.Id)
{
var autoroutePart = part.As<AutoroutePart>();
if (autoroutePart != null)
{
autoroutePart.PromoteToHomePage = false;
if (autoroutePart.IsPublished())
_orchardServices.ContentManager.Publish(autoroutePart.ContentItem);
}
}
// Update the home alias to point to this item being published.
_homeAliasService.PublishHomeAlias(part);
}
_autorouteService.Value.PublishAlias(part);
});
}
private void ProcessAlias(AutoroutePart part)
{
// Generate Alias for published content items
if (!part.HasDraft())
{
LocalizedString message = null;
// Generate an alias if one has not already been entered.
if (String.IsNullOrWhiteSpace(part.DisplayAlias))
{
part.DisplayAlias = _autorouteService.Value.GenerateAlias(part);
}
// Update the home alias to point to this item being published.
_homeAliasService.PublishHomeAlias(part);
if (String.IsNullOrWhiteSpace(part.DisplayAlias))
{
_autorouteService.Value.ProcessPath(part);
message = T("The permalink could not be generated, a new slug has been defined: \"{0}\"", part.Path);
return;
}
// Check for permalink conflict, unless we are trying to set the home page.
var previous = part.Path;
if (!_autorouteService.Value.ProcessPath(part))
message =
T("Permalinks in conflict. \"{0}\" is already set for a previously created {2} so now it has the slug \"{1}\"",
previous, part.Path, part.ContentItem.ContentType);
if (message != null)
{
_orchardServices.Notifier.Warning(message);
}
}
_autorouteService.Value.PublishAlias(part);
}
private void ProcessAlias(AutoroutePart part) {
// Generate an alias if one as not already been entered.
if (String.IsNullOrWhiteSpace(part.DisplayAlias)) {
part.DisplayAlias = _autorouteService.Value.GenerateAlias(part);
}
// If the generated alias is empty, compute a new one.
if (String.IsNullOrWhiteSpace(part.DisplayAlias)) {
_autorouteService.Value.ProcessPath(part);
_orchardServices.Notifier.Warning(T("The permalink could not be generated, a new slug has been defined: \"{0}\"", part.Path));
return;
}
// Check for permalink conflict, unless we are trying to set the home page.
var previous = part.Path;
if (!_autorouteService.Value.ProcessPath(part))
_orchardServices.Notifier.Warning(
T("Permalinks in conflict. \"{0}\" is already set for a previously created {2} so now it has the slug \"{1}\"",
previous, part.Path, part.ContentItem.ContentType));
}
void RemoveAlias(AutoroutePart part) {
var homePageId = _homeAliasService.GetHomePageId(VersionOptions.Latest);
void RemoveAlias(AutoroutePart part)
{
var homePageId = _homeAliasService.GetHomePageId();
// Is this the current home page?
if (part.ContentItem.Id == homePageId) {
if (part.ContentItem.Id == homePageId)
{
_orchardServices.Notifier.Warning(T("You removed the content item that served as the site's home page. \nMost possibly this means that instead of the home page a \"404 Not Found\" page will be displayed. \n\nTo prevent this you can e.g. publish a content item that has the \"Set as home page\" checkbox ticked."));
}
_autorouteService.Value.RemoveAliases(part);
_lockingProvider.Lock(LockString,
() => { _autorouteService.Value.RemoveAliases(part); });
}
}
}
}

View File

@@ -32,14 +32,13 @@ namespace Orchard.Autoroute.Recipes.Builders {
public override void Build(BuildContext context) {
var homeAliasRoute = _homeAliasService.GetHomeRoute() ?? new RouteValueDictionary();
var root = new XElement("HomeAlias");
var homePage = _homeAliasService.GetHomePage(VersionOptions.Latest);
var homePageId = _homeAliasService.GetHomePageId();
// If the home alias points to a content item, store its identifier in addition to the routevalues,
// so we can publish the home page alias during import where the ID primary key value of the home page might have changed,
// so we can't rely on the route values in that case.
if (homePage != null) {
var homePageIdentifier = _contentManager.GetItemMetadata(homePage).Identity.ToString();
root.Attr("Id", homePageIdentifier);
if (homePageId != null) {
root.Attr("Id", homePageId);
}
else {
// The alias does not point to a content item, so export the route values instead.

View File

@@ -11,8 +11,8 @@ using Orchard.ContentManagement.MetaData.Models;
using Orchard.Tokens;
using Orchard.Localization.Services;
using Orchard.Mvc;
using System.Web;
using Orchard.ContentManagement.Aspects;
using System.Web.Routing;
namespace Orchard.Autoroute.Services {
public class AutorouteService : Component, IAutorouteService {
@@ -54,8 +54,7 @@ namespace Orchard.Autoroute.Services {
// If we are editing an existing content item.
if (part.Record.Id != 0) {
ContentItem contentItem = _contentManager.Get(part.Record.ContentItemRecord.Id);
var aspect = contentItem.As<ILocalizableAspect>();
var aspect = part.As<ILocalizableAspect>();
if (aspect != null) {
itemCulture = aspect.Culture;
@@ -194,11 +193,9 @@ namespace Orchard.Autoroute.Services {
: part.Path;
}
public IEnumerable<AutoroutePart> GetSimilarPaths(string path) {
public IEnumerable<Tuple<string, RouteValueDictionary>> GetSimilarPaths(string path) {
return
_contentManager.Query<AutoroutePart, AutoroutePartRecord>()
.Where(part => part.DisplayAlias != null && part.DisplayAlias.StartsWith(path))
.List();
_aliasService.List().Where(x => x.Item1 != null && x.Item1.StartsWith(path) && x.Item2["area"].ToString() == "Contents").ToList();
}
public bool IsPathValid(string slug) {
@@ -210,11 +207,11 @@ namespace Orchard.Autoroute.Services {
// Don't include *this* part in the list
// of slugs to consider for conflict detection.
pathsLikeThis = pathsLikeThis.Where(p => p.ContentItem.Id != part.ContentItem.Id).ToArray();
pathsLikeThis = pathsLikeThis.Where(p => Convert.ToInt32(p.Item2["Id"]) != part.ContentItem.Id).ToArray();
if (pathsLikeThis.Any()) {
var originalPath = part.Path;
var newPath = GenerateUniqueSlug(part, pathsLikeThis.Select(p => p.Path));
var newPath = GenerateUniqueSlug(part, pathsLikeThis.Select(p => p.Item1));
part.DisplayAlias = newPath;
if (originalPath != newPath)

View File

@@ -31,28 +31,13 @@ namespace Orchard.Autoroute.Services {
return _homeAliasRoute;
}
public int? GetHomePageId(VersionOptions version = null) {
var homePage = GetHomePage(version);
return homePage != null ? homePage.Id : default(int?);
public int? GetHomePageId() {
int? homePageId = Convert.ToInt32(GetHomeRoute().Last().Value);
return homePageId;
}
public IContent GetHomePage(VersionOptions version = null) {
var homePageRoute = GetHomeRoute();
if (homePageRoute == null)
return null;
var alias = LookupAlias(homePageRoute);
if (alias == null)
return null;
var homePage = _contentManager.Query<AutoroutePart, AutoroutePartRecord>(version).Where(x => x.DisplayAlias == alias).Slice(0, 1).FirstOrDefault();
return homePage;
}
public bool IsHomePage(IContent content, VersionOptions homePageVersion = null) {
var homePageId = GetHomePageId(homePageVersion);
public bool IsHomePage(IContent content) {
var homePageId = GetHomePageId();
return content.Id == homePageId;
}

View File

@@ -5,9 +5,8 @@ namespace Orchard.Autoroute.Services {
public interface IHomeAliasService : IDependency {
RouteValueDictionary GetHomeRoute();
int? GetHomePageId(VersionOptions version = null);
IContent GetHomePage(VersionOptions version = null);
bool IsHomePage(IContent content, VersionOptions homePageVersion = null);
int? GetHomePageId();
bool IsHomePage(IContent content);
void PublishHomeAlias(IContent content);
void PublishHomeAlias(string route);
void PublishHomeAlias(RouteValueDictionary route);

View File

@@ -9,37 +9,59 @@ using Orchard.Layouts.Framework.Elements;
using Orchard.Layouts.Helpers;
using Orchard.Layouts.Models;
using Orchard.Layouts.ViewModels;
using Orchard.Locking;
using Orchard.Utility.Extensions;
namespace Orchard.Layouts.Services {
public class LayoutEditorFactory : ILayoutEditorFactory {
namespace Orchard.Layouts.Services
{
public class LayoutEditorFactory : ILayoutEditorFactory
{
private readonly ILayoutModelMapper _mapper;
private readonly ILayoutManager _layoutManager;
private readonly IElementManager _elementManager;
private readonly IElementDisplay _elementDisplay;
private readonly IShapeDisplay _shapeDisplay;
private readonly IOrchardServices _orchardServices;
private readonly ILockingProvider _lockingProvider;
public LayoutEditorFactory(
ILayoutModelMapper mapper,
ILayoutManager layoutManager,
IElementManager elementManager,
IElementDisplay elementDisplay,
IShapeDisplay shapeDisplay) {
ILayoutModelMapper mapper,
ILayoutManager layoutManager,
IElementManager elementManager,
IElementDisplay elementDisplay,
IShapeDisplay shapeDisplay,
IOrchardServices orchardServices,
ILockingProvider lockingProvider)
{
_mapper = mapper;
_layoutManager = layoutManager;
_elementManager = elementManager;
_elementDisplay = elementDisplay;
_shapeDisplay = shapeDisplay;
_orchardServices = orchardServices;
_lockingProvider = lockingProvider;
}
public LayoutEditor Create(LayoutPart layoutPart) {
private string LockString {
get {
return string.Join(".",
_orchardServices.WorkContext?.CurrentSite?.BaseUrl ?? "",
_orchardServices.WorkContext?.CurrentSite?.SiteName ?? "",
"LayoutEditorFactory");
}
}
public LayoutEditor Create(LayoutPart layoutPart)
{
return Create(layoutPart.LayoutData, layoutPart.SessionKey, layoutPart.TemplateId, layoutPart);
}
public LayoutEditor Create(string layoutData, string sessionKey, int? templateId = null, IContent content = null) {
return new LayoutEditor {
Data = _mapper.ToEditorModel(layoutData, new DescribeElementsContext {Content = content}).ToJson(),
public LayoutEditor Create(string layoutData, string sessionKey, int? templateId = null, IContent content = null)
{
return new LayoutEditor
{
Data = _mapper.ToEditorModel(layoutData, new DescribeElementsContext { Content = content }).ToJson(),
ConfigurationData = GetConfigurationData(content),
TemplateId = templateId,
Content = content,
@@ -48,26 +70,40 @@ namespace Orchard.Layouts.Services {
};
}
private IList<LayoutPart> GetTemplates(IContent content = null) {
var query = _layoutManager.GetTemplates();
var layoutPart = content != null ? content.As<LayoutPart>() : null;
private IList<LayoutPart> GetTemplates(IContent content = null)
{
IEnumerable<LayoutPart> query = null;
if (layoutPart != null) {
query = query.Where(x => x.Id != layoutPart.Id);
}
_lockingProvider.Lock(string.Join(".", LockString, content.Id),
() =>
{
query = _layoutManager.GetTemplates();
var layoutPart = content != null ? content.As<LayoutPart>() : null;
if (layoutPart != null)
{
query = query.Where(x => x.Id != layoutPart.Id);
}
});
return query.ToList();
}
private string GetConfigurationData(IContent content) {
private string GetConfigurationData(IContent content)
{
var elementCategories = GetCategories(content).ToArray();
var config = new {
categories = elementCategories.Select(category => new {
var config = new
{
categories = elementCategories.Select(category => new
{
name = category.DisplayName.Text,
contentTypes = category.Elements.Where(x => !x.IsSystemElement).Select(descriptor => {
contentTypes = category.Elements.Where(x => !x.IsSystemElement).Select(descriptor =>
{
var element = _elementManager.ActivateElement(descriptor);
var map = _mapper.GetMapFor(element);
return new {
return new
{
label = descriptor.DisplayText.Text,
id = descriptor.TypeName,
type = map.LayoutElementType,
@@ -87,11 +123,13 @@ namespace Orchard.Layouts.Services {
return JToken.FromObject(config).ToString(Formatting.None);
}
private string RenderElement(Element element, DescribeElementsContext describeContext, string displayType = "Design") {
private string RenderElement(Element element, DescribeElementsContext describeContext, string displayType = "Design")
{
return _shapeDisplay.Display(_elementDisplay.DisplayElement(element, describeContext.Content, displayType));
}
private IEnumerable<CategoryDescriptor> GetCategories(IContent content) {
private IEnumerable<CategoryDescriptor> GetCategories(IContent content)
{
var describeContext = new DescribeElementsContext { Content = content };
var elementCategories = _elementManager.GetCategories(describeContext).ToArray();

View File

@@ -1,4 +1,5 @@
using System.Linq;
using System.IO;
using System.Linq;
using Orchard.Caching;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions;
@@ -18,7 +19,8 @@ namespace Orchard.OutputCache.Services {
private readonly IClock _clock;
private readonly ISignals _signals;
private string _root;
private string _content;
private string _metadata;
public FileSystemOutputCacheBackgroundTask(
IAppDataFolder appDataFolder,
@@ -32,22 +34,26 @@ namespace Orchard.OutputCache.Services {
_clock = clock;
_signals = signals;
_root = _appDataFolder.Combine("OutputCache", _shellSettings.Name);
_metadata = FileSystemOutputCacheStorageProvider.GetMetadataPath(appDataFolder, _shellSettings.Name);
_content = FileSystemOutputCacheStorageProvider.GetContentPath(appDataFolder, _shellSettings.Name);
}
public void Sweep() {
foreach(var filename in _appDataFolder.ListFiles(_root).ToArray()) {
var validUntilUtc = _cacheManager.Get(filename, context => {
_signals.When(filename);
foreach(var filename in _appDataFolder.ListFiles(_metadata).ToArray()) {
var hash = Path.GetFileName(filename);
var validUntilUtc = _cacheManager.Get(hash, context => {
_signals.When(hash);
using (var stream = _appDataFolder.OpenFile(filename)) {
var cacheItem = FileSystemOutputCacheStorageProvider.Deserialize(stream);
var cacheItem = FileSystemOutputCacheStorageProvider.DeserializeMetadata(stream);
return cacheItem.ValidUntilUtc;
}
});
if (_clock.UtcNow > validUntilUtc) {
_appDataFolder.DeleteFile(filename);
_appDataFolder.DeleteFile(_appDataFolder.Combine(_metadata, hash));
_appDataFolder.DeleteFile(_appDataFolder.Combine(_content, hash));
_signals.Trigger(filename);
}
}

View File

@@ -1,45 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.OutputCache.Models;
using Orchard.Environment.Extensions;
using Orchard.Logging;
using Orchard.Services;
using Orchard.FileSystems.AppData;
using Orchard.Environment.Configuration;
using System.Web;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Text;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions;
using Orchard.FileSystems.AppData;
using Orchard.Logging;
using Orchard.OutputCache.Models;
using Orchard.Services;
namespace Orchard.OutputCache.Services {
[OrchardFeature("Orchard.OutputCache.FileSystem")]
[OrchardSuppressDependency("Orchard.OutputCache.Services.DefaultCacheStorageProvider")]
/// <summary>
/// This class provides an implementation of <see cref="IOutputCacheStorageProvider"/>
/// based on the local App_Data folder, inside <c>OuputCache/{tenant}</c>. It is not
/// recommended when used in a server farm.
/// based on the local App_Data folder, inside <c>FileCache/{tenant}</c>. It is not
/// recommended when used in a server farm, unless the file system is share (Azure App Services).
/// The <see cref="CacheItem"/> instances are binary serialized.
/// </summary>
/// <remarks>
/// This provider doesn't implement quotas support yet.
/// This provider doesn't support quotas yet.
/// </remarks>
public class FileSystemOutputCacheStorageProvider : IOutputCacheStorageProvider {
private readonly IClock _clock;
private readonly IAppDataFolder _appDataFolder;
private readonly ShellSettings _shellSettings;
private readonly string _root;
private readonly string _metadata;
private readonly string _content;
public static char[] InvalidPathChars = { '/', '\\', ':', '*', '?', '>', '<', '|' };
public FileSystemOutputCacheStorageProvider(IClock clock, IAppDataFolder appDataFolder, ShellSettings shellSettings) {
_appDataFolder = appDataFolder;
_clock = clock;
_shellSettings = shellSettings;
_root = _appDataFolder.Combine("OutputCache", _shellSettings.Name);
_metadata = GetMetadataPath(appDataFolder, _shellSettings.Name);
_content = GetContentPath(appDataFolder, _shellSettings.Name);
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public void Set(string key, CacheItem cacheItem) {
Retry(() => {
if (cacheItem == null) {
@@ -50,82 +56,158 @@ namespace Orchard.OutputCache.Services {
return;
}
var filename = GetCacheItemFilename(key);
var hash = GetCacheItemFileHash(key);
using (var stream = Serialize(cacheItem)) {
lock (String.Intern(hash)) {
var filename = _appDataFolder.Combine(_content, hash);
using (var fileStream = _appDataFolder.CreateFile(filename)) {
stream.CopyTo(fileStream);
using (var writer = new BinaryWriter(fileStream)) {
fileStream.Write(cacheItem.Output, 0, cacheItem.Output.Length);
}
}
using (var stream = SerializeMetadata(cacheItem)) {
filename = _appDataFolder.Combine(_metadata, hash);
using (var fileStream = _appDataFolder.CreateFile(filename)) {
stream.CopyTo(fileStream);
}
}
}
});
}
public void Remove(string key) {
Retry(() => {
var filename = GetCacheItemFilename(key);
if (_appDataFolder.FileExists(filename)) {
_appDataFolder.DeleteFile(filename);
}
});
var hash = GetCacheItemFileHash(key);
lock (String.Intern(hash)) {
Retry(() => {
var filename = _appDataFolder.Combine(_metadata, hash);
if (_appDataFolder.FileExists(filename)) {
_appDataFolder.DeleteFile(filename);
}
});
Retry(() => {
var filename = _appDataFolder.Combine(_content, hash);
if (_appDataFolder.FileExists(filename)) {
_appDataFolder.DeleteFile(filename);
}
});
}
}
public void RemoveAll() {
foreach(var filename in _appDataFolder.ListFiles(_root)) {
if(_appDataFolder.FileExists(filename)) {
_appDataFolder.DeleteFile(filename);
foreach (var folder in new[] { _metadata, _content }) {
foreach (var filename in _appDataFolder.ListFiles(folder)) {
var hash = Path.GetFileName(filename);
lock (String.Intern(hash)) {
try {
if (_appDataFolder.FileExists(filename)) {
_appDataFolder.DeleteFile(filename);
}
}
catch (Exception e) {
Logger.Warning(e, "An error occured while deleting the file: {0}", filename);
}
}
}
}
}
public CacheItem GetCacheItem(string key) {
return Retry(() => {
var filename = GetCacheItemFilename(key);
var hash = GetCacheItemFileHash(key);
lock (String.Intern(hash)) {
var filename = _appDataFolder.Combine(_metadata, hash);
if (!_appDataFolder.FileExists(filename)) {
return null;
}
using (var stream = _appDataFolder.OpenFile(filename)) {
if (stream == null) {
if (!_appDataFolder.FileExists(filename)) {
return null;
}
return Deserialize(stream);
CacheItem cacheItem = null;
using (var stream = _appDataFolder.OpenFile(filename)) {
if (stream == null) {
return null;
}
cacheItem = DeserializeMetadata(stream);
// We compare the requested key and the one stored in the metadata
// as there could be key collisions with the hashed filenames.
if (!cacheItem.CacheKey.Equals(key)) {
return null;
}
}
filename = _appDataFolder.Combine(_content, hash);
using (var stream = _appDataFolder.OpenFile(filename)) {
if (stream == null) {
return null;
}
using(var ms = new MemoryStream()) {
stream.CopyTo(ms);
cacheItem.Output = ms.ToArray();
}
}
return cacheItem;
}
});
}
public IEnumerable<CacheItem> GetCacheItems(int skip, int count) {
return _appDataFolder.ListFiles(_root)
return _appDataFolder.ListFiles(_metadata)
.OrderBy(x => x)
.Skip(skip)
.Take(count)
.Select(filename => {
using (var stream = _appDataFolder.OpenFile(filename)) {
return Deserialize(stream);
return DeserializeMetadata(stream);
}
})
.ToList();
}
public int GetCacheItemsCount() {
return _appDataFolder.ListFiles(_root).Count();
}
private string GetCacheItemFilename(string key) {
return _appDataFolder.Combine(_root, HttpUtility.UrlEncode(key));
return _appDataFolder.ListFiles(_metadata).Count();
}
internal static MemoryStream Serialize(CacheItem item) {
BinaryFormatter binaryFormatter = new BinaryFormatter();
var memoryStream = new MemoryStream();
binaryFormatter.Serialize(memoryStream, item);
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
public static string GetMetadataPath(IAppDataFolder appDataFolder, string tenant) {
return appDataFolder.Combine("FileCache", tenant, "metadata");
}
internal static CacheItem Deserialize(Stream stream) {
public static string GetContentPath(IAppDataFolder appDataFolder, string tenant) {
return appDataFolder.Combine("FileCache", tenant, "content");
}
private string GetCacheItemFileHash(string key) {
// The key is typically too long to be useful, so we use a hash
using (var md5 = MD5.Create()) {
var keyBytes = Encoding.UTF8.GetBytes(key);
var hashedBytes = md5.ComputeHash(keyBytes);
var b64 = Convert.ToBase64String(hashedBytes);
return String.Join("-", b64.Split(InvalidPathChars, StringSplitOptions.RemoveEmptyEntries));
}
}
internal static MemoryStream SerializeMetadata(CacheItem item) {
var output = item.Output;
item.Output = new byte[0];
try {
BinaryFormatter binaryFormatter = new BinaryFormatter();
var memoryStream = new MemoryStream();
binaryFormatter.Serialize(memoryStream, item);
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
finally {
item.Output = output;
}
}
internal static CacheItem DeserializeMetadata(Stream stream) {
BinaryFormatter binaryFormatter = new BinaryFormatter();
var result = (CacheItem)binaryFormatter.Deserialize(stream);
return result;
@@ -138,7 +220,9 @@ namespace Orchard.OutputCache.Services {
var t = action();
return t;
}
catch {
catch(Exception e) {
Logger.Warning("An unexpected error occured, attempt # {0}, i", e);
if (i == retries) {
throw;
}
@@ -153,9 +237,12 @@ namespace Orchard.OutputCache.Services {
for(int i=1; i <= retries; i++) {
try {
action();
return;
}
catch {
if(i == retries) {
catch(Exception e) {
Logger.Warning("An unexpected error occured, attempt # {0}, i", e);
if (i == retries) {
throw;
}
}

View File

@@ -33,6 +33,7 @@ namespace Orchard.Projections.Tests.Services {
builder.RegisterType<FieldIndexPartHandler>().As<IContentHandler>();
builder.RegisterType<OrchardServices>().As<IOrchardServices>();
builder.RegisterType<DefaultContentManager>().As<IContentManager>();
builder.RegisterType<DraftFieldIndexService>().As<IDraftFieldIndexService>();
builder.RegisterType<FieldIndexService>().As<IFieldIndexService>();
builder.RegisterType<ThingHandler>().As<IContentHandler>();

View File

@@ -41,6 +41,7 @@ namespace Orchard.Projections.Tests.Services {
builder.RegisterType<FieldIndexPartHandler>().As<IContentHandler>();
builder.RegisterType<OrchardServices>().As<IOrchardServices>();
builder.RegisterType<DefaultContentManager>().As<IContentManager>();
builder.RegisterType<DraftFieldIndexService>().As<IDraftFieldIndexService>();
builder.RegisterType<FieldIndexService>().As<IFieldIndexService>();
builder.RegisterType<ThingHandler>().As<IContentHandler>();

View File

@@ -26,10 +26,6 @@ namespace Orchard.PublishLater.ViewModels {
}
}
public bool HasPublished {
get { return IsPublished || ContentItem.ContentManager.Get(ContentItem.Id, VersionOptions.Published) != null; }
}
public DateTimeEditor Editor { get; set; }
}
}

View File

@@ -7,7 +7,7 @@
}
<ul class="pageStatus">
@* Published or not *@
@if (publishLaterPart.HasPublished()) {
@if (publishLaterPart.IsPublished()) {
<li><img class="icon" src="@Href("~/Modules/Orchard.PublishLater/Content/Admin/images/online.gif")" alt="@T("Online")" title="@T("The page is currently online")" /> </li>
<li>@T("Published")&nbsp;&#124;&nbsp;</li>
}

View File

@@ -26,14 +26,12 @@
<Page ContentTypeSettings.Draftable="True" TypeIndexing.Indexes="Search">
<TitlePart/>
<TagsPart />
<LocalizationPart />
<AutoroutePart />
<MenuPart />
</Page>
<BlogPost ContentTypeSettings.Draftable="True" TypeIndexing.Indexes="Search">
<CommentsPart />
<TagsPart />
<LocalizationPart />
<TitlePart/>
<AutoroutePart />
</BlogPost>
@@ -66,4 +64,4 @@
menuitem create /MenuPosition:"0" /MenuText:"Home" /Url:"~/" /MenuName:"Main Menu"
widget create MenuWidget /Title:"Main Menu" /RenderTitle:false /Zone:"Navigation" /Position:"1" /Layer:"Default" /Identity:"MenuWidget1" /MenuName:"Main Menu"
</Command>
</Orchard>
</Orchard>

View File

@@ -23,7 +23,6 @@
<Types>
<Page ContentTypeSettings.Draftable="True" TypeIndexing.Indexes="Search">
<TagsPart />
<LocalizationPart />
<TitlePart/>
<AutoroutePart />
<MenuPart />
@@ -31,7 +30,6 @@
<BlogPost ContentTypeSettings.Draftable="True" TypeIndexing.Indexes="Search">
<CommentsPart />
<TagsPart />
<LocalizationPart />
<TitlePart/>
<AutoroutePart />
</BlogPost>

View File

@@ -9,6 +9,7 @@ using Orchard.ContentManagement;
using Orchard.Security;
using Orchard.Tags.Models;
using Orchard.UI.Notify;
using Orchard.Locking;
namespace Orchard.Tags.Services {
public class TagService : ITagService {
@@ -18,19 +19,24 @@ namespace Orchard.Tags.Services {
private readonly IAuthorizationService _authorizationService;
private readonly IOrchardServices _orchardServices;
private readonly ISessionFactoryHolder _sessionFactoryHolder;
private readonly ILockingProvider _lockingProvider;
public TagService(IRepository<TagRecord> tagRepository,
IRepository<ContentTagRecord> contentTagRepository,
INotifier notifier,
IAuthorizationService authorizationService,
IOrchardServices orchardServices,
ISessionFactoryHolder sessionFactoryHolder) {
ISessionFactoryHolder sessionFactoryHolder,
ILockingProvider lockingProvider) {
_tagRepository = tagRepository;
_contentTagRepository = contentTagRepository;
_notifier = notifier;
_authorizationService = authorizationService;
_orchardServices = orchardServices;
_sessionFactoryHolder = sessionFactoryHolder;
_lockingProvider = lockingProvider;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
@@ -56,11 +62,23 @@ namespace Orchard.Tags.Services {
}
public TagRecord CreateTag(string tagName) {
var result = _tagRepository.Get(x => x.TagName == tagName);
if (result == null) {
result = new TagRecord { TagName = tagName };
_tagRepository.Create(result);
}
TagRecord result = null;
var lockString = string.Join(".",
_orchardServices.WorkContext?.CurrentSite?.BaseUrl ?? "",
_orchardServices.WorkContext?.CurrentSite?.SiteName ?? "",
"TagService.CreateTag",
tagName);
_lockingProvider.Lock(lockString,
() => {
result = _tagRepository.Get(x => x.TagName == tagName);
if (result == null) {
result = new TagRecord { TagName = tagName };
_tagRepository.Create(result);
}
});
return result;
}

View File

@@ -62,10 +62,11 @@
$(this).closest("form").find(".apply-bulk-actions-auto:first").click();
});
$("body").on("click", "[itemprop~='RemoveUrl']", function () {
$("body").on("click", "[itemprop~='RemoveUrl']", function (e) {
// don't show the confirm dialog if the link is also UnsafeUrl, as it will already be handled in base.js
if ($(this).filter("[itemprop~='UnsafeUrl']").length == 1) {
return false;
e.preventDefault();
return;
}
// use a custom message if its set in data-message

View File

@@ -0,0 +1,237 @@
using System;
namespace Orchard.Locking {
public interface ILockingProvider : IDependency {
/// <summary>
/// Handles locking on a given object to execute the desired critical code. Optionally, it is possible
/// to tell how to manage exceptions, e.g. in a case where some cleanup may be required when the
/// critical code fails after having been partially executed.
/// </summary>
/// <param name="lockOn">The object upon which the lock will be created.</param>
/// <param name="criticalCode">The critical code to be executed while holding the lock.</param>
/// <param name="innerHandler">The (optional) action to be executed to handle exceptions while still
/// holding the lock.</param>
/// <param name="outerHandler">The (optional) action to be executed to handle exceptions after the
/// lock has been released.</param>
/// <exception cref="ArgumentNullException">Throws an ArgumentNullException the lockOn parameter is null.</exception>
/// <remarks>Internally, this method uses System.Threading.Monitor to delimit the execution of the
/// critical code. Unlike the implementation of lock(obj){}, this implementation allows handling of
/// exceptions both while holding and after releasing the lock on the object. The default behaviour
/// if both the Actions to handle exceptions are null, this method is the same as calling
/// lock(obj){criticalCode();}, meaning that it will bubble out the exception while holding the lock, and
/// only release it afterwards. If an innerHandler is provided, but outerHandler is null, an exception will
/// bubble out after the lock is released. To prevent exceptions from being thrown, both innerHandler and
/// outerHandler should be provided.</remarks>
void Lock(
object lockOn,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null);
/// <summary>
/// Handles locking on a given string to execute the desired critical code. Optionally, it is possible
/// to tell how to manage exceptions, e.g. in a case where some cleanup may be required when the
/// critical code fails after having been partially executed.
/// </summary>
/// <param name="lockOn">The string upon which the lock will be created.</param>
/// <param name="criticalCode">The critical code to be executed while holding the lock.</param>
/// <param name="innerHandler">The (optional) action to be executed to handle exceptions while still
/// holding the lock.</param>
/// <param name="outerHandler">The (optional) action to be executed to handle exceptions after the
/// lock has been released.</param>
/// <remarks>Internally, this method uses System.Threading.Monitor to delimit the execution of the
/// critical code. Unlike the implementation of lock(obj){}, this implementation allows handling of
/// exceptions both while holding and after releasing the lock on the object. The default behaviour
/// if both the Actions to handle exceptions are null, this method is the same as calling
/// lock(obj){criticalCode();}, meaning that it will bubble out the exception while holding the lock, and
/// only release it afterwards. If an innerHandler is provided, but outerHandler is null, an exception will
/// bubble out after the lock is released. To prevent exceptions from being thrown, both innerHandler and
/// outerHandler should be provided.</remarks>
void Lock(
string lockOn,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null);
/// <summary>
/// Handles locking on a given object to execute the desired critical code. Optionally, it is possible
/// to tell how to manage exceptions, e.g. in a case where some cleanup may be required when the
/// critical code fails after having been partially executed.
/// </summary>
/// <param name="lockOn">The object upon which the lock will be created.</param>
/// <param name="criticalCode">The critical code to be executed while holding the lock.</param>
/// <param name="innerHandler">The (optional) action to be executed to handle exceptions while still
/// holding the lock.</param>
/// <param name="outerHandler">The (optional) action to be executed to handle exceptions after the
/// lock has been released.</param>
/// <returns>true if the current thread acquires the lock; otherwise, false.</returns>
/// <exception cref="ArgumentNullException">Throws an ArgumentNullException the lockOn parameter is null.</exception>
/// <remarks>Internally, this method uses System.Threading.Monitor to delimit the execution of the
/// critical code. Unlike the implementation of lock(obj){}, this implementation allows handling of
/// exceptions both while holding and after releasing the lock on the object. The default behaviour
/// if both the Actions to handle exceptions are null, this method is the same as calling
/// lock(obj){criticalCode();}, meaning that it will bubble out the exception while holding the lock, and
/// only release it afterwards. If an innerHandler is provided, but outerHandler is null, an exception will
/// bubble out after the lock is released. To prevent exceptions from being thrown, both innerHandler and
/// outerHandler should be provided.</remarks>
bool TryLock(
object lockOn,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null);
/// <summary>
/// Handles locking on a given string to execute the desired critical code. Optionally, it is possible
/// to tell how to manage exceptions, e.g. in a case where some cleanup may be required when the
/// critical code fails after having been partially executed.
/// </summary>
/// <param name="lockOn">The string upon which the lock will be created.</param>
/// <param name="criticalCode">The critical code to be executed while holding the lock.</param>
/// <param name="innerHandler">The (optional) action to be executed to handle exceptions while still
/// holding the lock.</param>
/// <param name="outerHandler">The (optional) action to be executed to handle exceptions after the
/// lock has been released.</param>
/// <returns>true if the current thread acquires the lock; otherwise, false.</returns>
/// <remarks>Internally, this method uses System.Threading.Monitor to delimit the execution of the
/// critical code. Unlike the implementation of lock(obj){}, this implementation allows handling of
/// exceptions both while holding and after releasing the lock on the object. The default behaviour
/// if both the Actions to handle exceptions are null, this method is the same as calling
/// lock(obj){criticalCode();}, meaning that it will bubble out the exception while holding the lock, and
/// only release it afterwards. If an innerHandler is provided, but outerHandler is null, an exception will
/// bubble out after the lock is released. To prevent exceptions from being thrown, both innerHandler and
/// outerHandler should be provided.</remarks>
bool TryLock(
string lockOn,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null);
/// <summary>
/// Handles locking on a given object to execute the desired critical code. Optionally, it is possible
/// to tell how to manage exceptions, e.g. in a case where some cleanup may be required when the
/// critical code fails after having been partially executed.
/// </summary>
/// <param name="lockOn">The object upon which the lock will be created.</param>
/// <param name="timeout">A TimeSpan representing the amount of time to wait for the lock. A value
/// of 1 millisecond specifies an infinite wait.</param>
/// <param name="criticalCode">The critical code to be executed while holding the lock.</param>
/// <param name="innerHandler">The (optional) action to be executed to handle exceptions while still
/// holding the lock.</param>
/// <param name="outerHandler">The (optional) action to be executed to handle exceptions after the
/// lock has been released.</param>
/// <returns>true if the current thread acquires the lock; otherwise, false.</returns>
/// <exception cref="ArgumentNullException">Throws an ArgumentNullException the lockOn parameter is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Throws an ArgumentOutOfRangeException if the value of timeout
/// in milliseconds is negative and is not equal to Infinite (-1 millisecond), or is greater than MaxValue.</exception>
/// <remarks>Internally, this method uses System.Threading.Monitor to delimit the execution of the
/// critical code. Unlike the implementation of lock(obj){}, this implementation allows handling of
/// exceptions both while holding and after releasing the lock on the object. The default behaviour
/// if both the Actions to handle exceptions are null, this method is the same as calling
/// lock(obj){criticalCode();}, meaning that it will bubble out the exception while holding the lock, and
/// only release it afterwards. If an innerHandler is provided, but outerHandler is null, an exception will
/// bubble out after the lock is released. To prevent exceptions from being thrown, both innerHandler and
/// outerHandler should be provided.</remarks>
bool TryLock(
object lockOn,
TimeSpan timeout,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null);
/// <summary>
/// Handles locking on a given string to execute the desired critical code. Optionally, it is possible
/// to tell how to manage exceptions, e.g. in a case where some cleanup may be required when the
/// critical code fails after having been partially executed.
/// </summary>
/// <param name="lockOn">The string upon which the lock will be created.</param>
/// <param name="timeout">A TimeSpan representing the amount of time to wait for the lock. A value
/// of 1 millisecond specifies an infinite wait.</param>
/// <param name="criticalCode">The critical code to be executed while holding the lock.</param>
/// <param name="innerHandler">The (optional) action to be executed to handle exceptions while still
/// holding the lock.</param>
/// <param name="outerHandler">The (optional) action to be executed to handle exceptions after the
/// lock has been released.</param>
/// <returns>true if the current thread acquires the lock; otherwise, false.</returns>
/// <exception cref="ArgumentNullException">Throws an ArgumentNullException the lockOn parameter is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Throws an ArgumentOutOfRangeException if the value of timeout
/// in milliseconds is negative and is not equal to Infinite (-1 millisecond), or is greater than MaxValue.</exception>
/// <remarks>Internally, this method uses System.Threading.Monitor to delimit the execution of the
/// critical code. Unlike the implementation of lock(obj){}, this implementation allows handling of
/// exceptions both while holding and after releasing the lock on the object. The default behaviour
/// if both the Actions to handle exceptions are null, this method is the same as calling
/// lock(obj){criticalCode();}, meaning that it will bubble out the exception while holding the lock, and
/// only release it afterwards. If an innerHandler is provided, but outerHandler is null, an exception will
/// bubble out after the lock is released. To prevent exceptions from being thrown, both innerHandler and
/// outerHandler should be provided.</remarks>
bool TryLock(
string lockOn,
TimeSpan timeout,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null);
/// <summary>
/// Handles locking on a given object to execute the desired critical code. Optionally, it is possible
/// to tell how to manage exceptions, e.g. in a case where some cleanup may be required when the
/// critical code fails after having been partially executed.
/// </summary>
/// <param name="lockOn">The object upon which the lock will be created.</param>
/// <param name="millisecondsTimeout">The number of milliseconds to wait for the lock..</param>
/// <param name="criticalCode">The critical code to be executed while holding the lock.</param>
/// <param name="innerHandler">The (optional) action to be executed to handle exceptions while still
/// holding the lock.</param>
/// <param name="outerHandler">The (optional) action to be executed to handle exceptions after the
/// lock has been released.</param>
/// <returns>true if the current thread acquires the lock; otherwise, false.</returns>
/// <exception cref="ArgumentNullException">Throws an ArgumentNullException the lockOn parameter is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Throws an ArgumentOutOfRangeException if the value
/// of millisecondsTimeout is negative, and not equal to Infinite.</exception>
/// <remarks>Internally, this method uses System.Threading.Monitor to delimit the execution of the
/// critical code. Unlike the implementation of lock(obj){}, this implementation allows handling of
/// exceptions both while holding and after releasing the lock on the object. The default behaviour
/// if both the Actions to handle exceptions are null, this method is the same as calling
/// lock(obj){criticalCode();}, meaning that it will bubble out the exception while holding the lock, and
/// only release it afterwards. If an innerHandler is provided, but outerHandler is null, an exception will
/// bubble out after the lock is released. To prevent exceptions from being thrown, both innerHandler and
/// outerHandler should be provided.</remarks>
bool TryLock(
object lockOn,
int millisecondsTimeout,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null);
/// <summary>
/// Handles locking on a given string to execute the desired critical code. Optionally, it is possible
/// to tell how to manage exceptions, e.g. in a case where some cleanup may be required when the
/// critical code fails after having been partially executed.
/// </summary>
/// <param name="lockOn">The string upon which the lock will be created.</param>
/// <param name="millisecondsTimeout">The number of milliseconds to wait for the lock..</param>
/// <param name="criticalCode">The critical code to be executed while holding the lock.</param>
/// <param name="innerHandler">The (optional) action to be executed to handle exceptions while still
/// holding the lock.</param>
/// <param name="outerHandler">The (optional) action to be executed to handle exceptions after the
/// lock has been released.</param>
/// <returns>true if the current thread acquires the lock; otherwise, false.</returns>
/// <exception cref="ArgumentNullException">Throws an ArgumentNullException the lockOn parameter is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Throws an ArgumentOutOfRangeException if the value
/// of millisecondsTimeout is negative, and not equal to Infinite.</exception>
/// <remarks>Internally, this method uses System.Threading.Monitor to delimit the execution of the
/// critical code. Unlike the implementation of lock(obj){}, this implementation allows handling of
/// exceptions both while holding and after releasing the lock on the object. The default behaviour
/// if both the Actions to handle exceptions are null, this method is the same as calling
/// lock(obj){criticalCode();}, meaning that it will bubble out the exception while holding the lock, and
/// only release it afterwards. If an innerHandler is provided, but outerHandler is null, an exception will
/// bubble out after the lock is released. To prevent exceptions from being thrown, both innerHandler and
/// outerHandler should be provided.</remarks>
bool TryLock(
string lockOn,
int millisecondsTimeout,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null);
}
}

View File

@@ -0,0 +1,251 @@
using System;
using System.Threading;
using Orchard.Localization;
using Orchard.Logging;
namespace Orchard.Locking {
public class LockingProvider : ILockingProvider {
public LockingProvider() {
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
public ILogger Logger { get; set; }
public Localizer T { get; set; }
public void Lock(
object lockOn,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null) {
LockInternal(lockOn, criticalCode, innerHandler, outerHandler);
}
public void Lock(
string lockOn,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null) {
LockInternal(String.Intern(lockOn), criticalCode, innerHandler, outerHandler);
}
public bool TryLock(
object lockOn,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null) {
return TryLockInternal(lockOn, TimeSpan.Zero, criticalCode, innerHandler, outerHandler);
}
public bool TryLock(
string lockOn,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null) {
return TryLockInternal(String.Intern(lockOn), TimeSpan.Zero, criticalCode, innerHandler, outerHandler);
}
public bool TryLock(
object lockOn,
TimeSpan timeout,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null) {
return TryLockInternal(lockOn, timeout, criticalCode, innerHandler, outerHandler);
}
public bool TryLock(
string lockOn,
TimeSpan timeout,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null) {
return TryLockInternal(String.Intern(lockOn), timeout, criticalCode, innerHandler, outerHandler);
}
public bool TryLock(
object lockOn,
int millisecondsTimeout,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null) {
return TryLockInternal(lockOn, millisecondsTimeout, criticalCode, innerHandler, outerHandler);
}
public bool TryLock(
string lockOn,
int millisecondsTimeout,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null) {
return TryLockInternal(String.Intern(lockOn), millisecondsTimeout, criticalCode, innerHandler, outerHandler);
}
private void LockInternal(
object lockOn,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null) {
bool taken = false;
var tmp = lockOn;
Exception outerException = null;
try {
Monitor.Enter(tmp, ref taken);
criticalCode?.Invoke();
}
catch (Exception ex) {
outerException = ex;
CleanLog(ex);
if (innerHandler != null) {
innerHandler.Invoke(ex);
}
else {
if (outerHandler == null) {
// if both the handlers are null, the methods should behave like lock(tmp){}
// and only bubble out the exception while holding the lock.
outerException = null;
}
throw ex;
}
}
finally {
if (taken) {
Monitor.Exit(tmp);
}
}
// Even if there was an handler for the exception to be used in the critical section
// (i.e. innerHandler != null) we have further handling here. This may simply mean throwing
// the exception out when outerHandler == null
if (outerException != null) {
if (outerHandler != null) {
outerHandler.Invoke(outerException);
}
else {
throw outerException;
}
}
}
private bool TryLockInternal(
object lockOn,
TimeSpan timeout,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null) {
var tmp = lockOn;
Exception outerException = null;
if (Monitor.TryEnter(tmp, timeout)) {
try {
criticalCode?.Invoke();
}
catch (Exception ex) {
outerException = ex;
CleanLog(ex);
if (innerHandler != null) {
innerHandler.Invoke(ex);
}
else {
if (outerHandler == null) {
// if both the handlers are null, the methods should behave like lock(tmp){}
// and only bubble out the exception while holding the lock.
outerException = null;
}
throw ex;
}
}
finally {
Monitor.Exit(tmp);
}
// Even if there was an handler for the exception to be used in the critical section
// (i.e. innerHandler != null) we have further handling here. This may simply mean throwing
// the exception out when outerHandler == null
if (outerException != null) {
if (outerHandler != null) {
outerHandler.Invoke(outerException);
}
else {
throw outerException;
}
}
return true;
}
return false;
}
private bool TryLockInternal(
object lockOn,
int millisecondsTimeout,
Action criticalCode,
Action<Exception> innerHandler = null,
Action<Exception> outerHandler = null) {
var tmp = lockOn;
Exception outerException = null;
if (Monitor.TryEnter(tmp, millisecondsTimeout)) {
try {
criticalCode?.Invoke();
}
catch (Exception ex) {
outerException = ex;
CleanLog(ex);
if (innerHandler != null) {
innerHandler.Invoke(ex);
}
else {
if (outerHandler == null) {
// if both the handlers are null, the methods should behave like lock(tmp){}
// and only bubble out the exception while holding the lock.
outerException = null;
}
throw ex;
}
}
finally {
Monitor.Exit(tmp);
}
// Even if there was an handler for the exception to be used in the critical section
// (i.e. innerHandler != null) we have further handling here. This may simply mean throwing
// the exception out when outerHandler == null
if (outerException != null) {
if (outerHandler != null) {
outerHandler.Invoke(outerException);
}
else {
throw outerException;
}
}
return true;
}
return false;
}
private void CleanLog(Exception ex) {
try {
Logger.Log(Logging.LogLevel.Error, ex, T("Exception while running critical code").Text);
}
catch (Exception) {
// prevent messing things up if the logger fails
}
}
}
}

View File

@@ -185,6 +185,8 @@
<Compile Include="Data\Migration\Schema\DropUniqueConstraintCommand.cs" />
<Compile Include="Environment\Extensions\Models\LifecycleStatus.cs" />
<Compile Include="Environment\ShellBuilders\ICompositionStrategy.cs" />
<Compile Include="Locking\ILockingProvider.cs" />
<Compile Include="Locking\LockingProvider.cs" />
<Compile Include="Mvc\Updater.cs" />
<Compile Include="Recipes\Models\ConfigurationContext.cs" />
<Compile Include="Recipes\Models\RecipeBuilderStepConfigurationContext.cs" />