mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-07 16:13:58 +08:00
Compare commits
19 Commits
99ce4ca61e
...
autoroute-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0ae9b4b4fe | ||
![]() |
9446665c95 | ||
![]() |
df69a86d11 | ||
![]() |
01481bad3c | ||
![]() |
5133a3cf49 | ||
![]() |
a9b184a301 | ||
![]() |
9e98c8f6e6 | ||
![]() |
6c48096e46 | ||
![]() |
a93fd9fee9 | ||
![]() |
0b1dee0c15 | ||
![]() |
d12554fbbc | ||
![]() |
f410146e54 | ||
![]() |
b775031b6b | ||
![]() |
76e4fd5034 | ||
![]() |
6da6368bcc | ||
![]() |
e3f010d62b | ||
![]() |
1908fff595 | ||
![]() |
cc5ffcd313 | ||
![]() |
b0db0454a5 |
@@ -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
|
||||
|
6
src/Orchard.Specs/Boolean.feature.cs
generated
6
src/Orchard.Specs/Boolean.feature.cs
generated
@@ -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 ");
|
||||
|
@@ -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
|
||||
|
28
src/Orchard.Specs/DateTime.feature.cs
generated
28
src/Orchard.Specs/DateTime.feature.cs
generated
@@ -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 ");
|
||||
|
@@ -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\""
|
||||
|
28
src/Orchard.Specs/Enumeration.feature.cs
generated
28
src/Orchard.Specs/Enumeration.feature.cs
generated
@@ -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 ");
|
||||
|
@@ -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\""
|
22
src/Orchard.Specs/Input.feature.cs
generated
22
src/Orchard.Specs/Input.feature.cs
generated
@@ -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 ");
|
||||
|
@@ -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\""
|
10
src/Orchard.Specs/Link.feature.cs
generated
10
src/Orchard.Specs/Link.feature.cs
generated
@@ -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 ");
|
||||
|
@@ -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
|
||||
|
8
src/Orchard.Specs/MediaPicker.feature.cs
generated
8
src/Orchard.Specs/MediaPicker.feature.cs
generated
@@ -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 ");
|
||||
|
@@ -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 'a' is not valid for Minimum."
|
||||
And I should see "The value 'b' 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\""
|
18
src/Orchard.Specs/Numeric.feature.cs
generated
18
src/Orchard.Specs/Numeric.feature.cs
generated
@@ -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 ");
|
||||
|
@@ -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\""
|
12
src/Orchard.Specs/Text.feature.cs
generated
12
src/Orchard.Specs/Text.feature.cs
generated
@@ -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 ");
|
||||
|
@@ -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<>));
|
||||
}
|
||||
|
||||
|
@@ -45,7 +45,7 @@ namespace Orchard.Tests.Stubs {
|
||||
}
|
||||
|
||||
public string SiteName {
|
||||
get { throw new NotImplementedException(); }
|
||||
get { return "TestSite"; }
|
||||
}
|
||||
|
||||
public string SiteSalt {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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.
|
||||
|
@@ -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)
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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>();
|
||||
|
@@ -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>();
|
||||
|
@@ -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; }
|
||||
}
|
||||
}
|
@@ -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") | </li>
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
237
src/Orchard/Locking/ILockingProvider.cs
Normal file
237
src/Orchard/Locking/ILockingProvider.cs
Normal 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);
|
||||
|
||||
}
|
||||
}
|
251
src/Orchard/Locking/LockingProvider.cs
Normal file
251
src/Orchard/Locking/LockingProvider.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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" />
|
||||
|
Reference in New Issue
Block a user