mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-01-19 17:51:45 +08:00
Merge branch '1.10.x' into dev
Conflicts: ClickToBuildAzurePackage.cmd Orchard.proj src/Orchard.Azure/Orchard.Azure.sln src/Orchard.Web/Modules/Orchard.Taxonomies/Controllers/TermAdminController.cs src/Orchard.Web/Modules/Orchard.Users/Controllers/AdminController.cs
This commit is contained in:
@@ -23,13 +23,14 @@ echo "Unable to detect suitable environment. Build may not succeed."
|
||||
|
||||
SET target=%1
|
||||
SET project=%2
|
||||
SET solution=%3
|
||||
|
||||
IF "%target%" == "" SET target=Build
|
||||
IF "%project%" =="" SET project=Orchard.proj
|
||||
IF "%project%" == "" SET project=Orchard.proj
|
||||
IF "%solution%" == "" SET solution=src\Orchard.sln
|
||||
|
||||
lib\nuget\nuget.exe restore .\src\Orchard.sln
|
||||
lib\nuget\nuget.exe restore %solution%
|
||||
|
||||
msbuild /t:%target% %project%
|
||||
msbuild /t:%target% %project% /p:Solution=%solution%
|
||||
|
||||
pause
|
||||
|
||||
|
||||
19
Orchard.proj
19
Orchard.proj
@@ -6,6 +6,7 @@
|
||||
<PropertyGroup>
|
||||
<LibFolder>$(MSBuildProjectDirectory)\lib</LibFolder>
|
||||
<SrcFolder>$(MSBuildProjectDirectory)\src</SrcFolder>
|
||||
<AzureSrcFolder>$(SrcFolder)\Orchard.Azure</AzureSrcFolder>
|
||||
<BuildFolder>$(MSBuildProjectDirectory)\build</BuildFolder>
|
||||
<MsBuildTasksFolder>$(MSBuildProjectDirectory)\buildtasks</MsBuildTasksFolder>
|
||||
<ArtifactsFolder>$(MSBuildProjectDirectory)\artifacts</ArtifactsFolder>
|
||||
@@ -29,6 +30,10 @@
|
||||
<BuildPlatform Condition="$(BuildPlatform) == ''">x86</BuildPlatform>
|
||||
<Configuration Condition="$(Configuration) == ''">Release</Configuration>
|
||||
|
||||
<OrchardSolution>$(SrcFolder)\Orchard.sln</OrchardSolution>
|
||||
<OrchardAzureSolution>$(AzureSrcFolder)\Orchard.Azure.sln</OrchardAzureSolution>
|
||||
<Solution Condition="$(Solution) == ''">$(OrchardSolution)</Solution>
|
||||
|
||||
<!-- TeamCity build number -->
|
||||
<Version>$(BUILD_NUMBER)</Version>
|
||||
</PropertyGroup>
|
||||
@@ -38,7 +43,7 @@
|
||||
|
||||
<!-- Coordinating Targets -->
|
||||
|
||||
<Target Name ="Build">
|
||||
<Target Name="Build">
|
||||
<CallTarget Targets="Clean"/>
|
||||
<CallTarget Targets="Compile"/>
|
||||
<CallTarget Targets="Test"/>
|
||||
@@ -96,7 +101,7 @@
|
||||
<!-- Building -->
|
||||
|
||||
<Target Name="Clean">
|
||||
<MSBuild Projects="$(SrcFolder)\Orchard.sln" Targets="Clean" />
|
||||
<MSBuild Projects="$(Solution)" Targets="Clean" />
|
||||
<RemoveDir Directories="$(BuildFolder)" ContinueOnError="true"/>
|
||||
<RemoveDir Directories="$(ArtifactsFolder)" />
|
||||
</Target>
|
||||
@@ -104,16 +109,16 @@
|
||||
<Target Name="Compile">
|
||||
<!-- Compile to "OutputFolder" -->
|
||||
<MSBuild
|
||||
Projects="$(SrcFolder)\Orchard.sln"
|
||||
Projects="$(Solution)"
|
||||
Targets="Build"
|
||||
Properties="Configuration=$(Configuration);OutputPath=$(CompileFolder)" />
|
||||
<!-- Compile to "regular" output folder for devs using VS locally -->
|
||||
<MSBuild
|
||||
Projects="$(SrcFolder)\Orchard.sln"
|
||||
Projects="$(Solution)"
|
||||
Targets="Build"/>
|
||||
</Target>
|
||||
|
||||
<Target Name ="CompileMsBuildTasks">
|
||||
<Target Name="CompileMsBuildTasks">
|
||||
<MSBuild
|
||||
Projects="$(SrcFolder)\Tools\MSBuild.Orchard.Tasks\MSBuild.Orchard.Tasks.csproj"
|
||||
Targets="Build"
|
||||
@@ -122,7 +127,7 @@
|
||||
|
||||
<!-- Testing -->
|
||||
|
||||
<Target Name ="Test">
|
||||
<Target Name="Test">
|
||||
<!-- TeamCity support -->
|
||||
<ItemGroup>
|
||||
<NUnitAddinFiles Include="$(teamcity_dotnet_nunitaddin)-2.5.2.*" />
|
||||
@@ -137,7 +142,7 @@
|
||||
<NUnit Assemblies="@(TestAssemblies)" ToolPath="$(LibFolder)\nunit" WorkingDirectory="$(CompileFolder)" OutputXmlFile="$(BuildFolder)\Orchard.Tests.xml" ExcludeCategory="longrunning" />
|
||||
</Target>
|
||||
|
||||
<Target Name ="Spec" DependsOnTargets="Package-Stage">
|
||||
<Target Name="Spec" DependsOnTargets="Package-Stage">
|
||||
<!-- TeamCity support -->
|
||||
<ItemGroup>
|
||||
<NUnitAddinFiles Include="$(teamcity_dotnet_nunitaddin)-2.5.2.*" />
|
||||
|
||||
@@ -25,6 +25,7 @@ Scenario: A new tenant is created
|
||||
And I fill in
|
||||
| name | value |
|
||||
| Name | Scott |
|
||||
| RequestUrlPrefix | scott |
|
||||
And I hit "Save"
|
||||
And I am redirected
|
||||
Then I should see "<h3>Scott\s*</h3>"
|
||||
@@ -37,6 +38,7 @@ Scenario: A new tenant is created with uninitialized state
|
||||
And I fill in
|
||||
| name | value |
|
||||
| Name | Scott |
|
||||
| RequestUrlPrefix | scott |
|
||||
And I hit "Save"
|
||||
And I am redirected
|
||||
Then I should see "<li class="tenant Uninitialized">"
|
||||
|
||||
6
src/Orchard.Specs/MultiTenancy.feature.cs
generated
6
src/Orchard.Specs/MultiTenancy.feature.cs
generated
@@ -129,6 +129,9 @@ this.ScenarioSetup(scenarioInfo);
|
||||
table1.AddRow(new string[] {
|
||||
"Name",
|
||||
"Scott"});
|
||||
table1.AddRow(new string[] {
|
||||
"RequestUrlPrefix",
|
||||
"scott"});
|
||||
#line 25
|
||||
testRunner.And("I fill in", ((string)(null)), table1, "And ");
|
||||
#line 28
|
||||
@@ -163,6 +166,9 @@ this.ScenarioSetup(scenarioInfo);
|
||||
table2.AddRow(new string[] {
|
||||
"Name",
|
||||
"Scott"});
|
||||
table2.AddRow(new string[] {
|
||||
"RequestUrlPrefix",
|
||||
"scott"});
|
||||
#line 37
|
||||
testRunner.And("I fill in", ((string)(null)), table2, "And ");
|
||||
#line 40
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
</Properties>
|
||||
</Component>
|
||||
|
||||
<Component Type="Orchard.Services.ClientAddressAccessor">
|
||||
<Component Type="Orchard.Services.ClientHostAddressAccessor">
|
||||
<Properties>
|
||||
<!-- Set Value="true" to read the client host address from the specified HTTP header. -->
|
||||
<Property Name="EnableClientHostAddressHeader" Value="false"/>
|
||||
@@ -120,5 +120,13 @@
|
||||
</Properties>
|
||||
</Component>
|
||||
|
||||
<Component Type="Orchard.Environment.DefaultOrchardHost">
|
||||
<Properties>
|
||||
<!-- The number of retries when a tenant can't be loaded -->
|
||||
<Property Name="Retries" Value="1"/>
|
||||
<Property Name="DelayRetries" Value="true"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
|
||||
</Components>
|
||||
</HostComponents>
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using Orchard.Environment;
|
||||
using Orchard.Tasks;
|
||||
using Orchard.Logging;
|
||||
|
||||
namespace Orchard.Alias.Implementation.Updater {
|
||||
public class AliasHolderUpdaterTask : IOrchardShellEvents, IBackgroundTask {
|
||||
private readonly IAliasHolderUpdater _aliasHolderUpdater;
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public AliasHolderUpdaterTask(IAliasHolderUpdater aliasHolderUpdater) {
|
||||
_aliasHolderUpdater = aliasHolderUpdater;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
void IOrchardShellEvents.Activated() {
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void IOrchardShellEvents.Terminating() {
|
||||
}
|
||||
|
||||
private void Refresh() {
|
||||
try {
|
||||
_aliasHolderUpdater.Refresh();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Logger.Error(ex, "Exception during Alias refresh");
|
||||
}
|
||||
}
|
||||
|
||||
public void Sweep() {
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,6 @@
|
||||
<Compile Include="Implementation\Holder\IAliasHolder.cs" />
|
||||
<Compile Include="Implementation\Map\AliasMap.cs" />
|
||||
<Compile Include="Implementation\Storage\AliasStorage.cs" />
|
||||
<Compile Include="Implementation\Updater\AliasHolderUpdaterTask.cs" />
|
||||
<Compile Include="Implementation\Utils.cs" />
|
||||
<Compile Include="Migrations.cs" />
|
||||
<Compile Include="Recipes\Builders\AliasStep.cs" />
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
jQuery('#btn-@Html.FieldIdFor(m => m.ContentItemId)').trigger("orchard-admin-contentpicker-open", {
|
||||
callback: function(data) {
|
||||
|
||||
data = Array.isArray && Array.isArray(data) ? data[0] : data;
|
||||
jQuery('#@Html.FieldIdFor(m => m.ContentItemId)').val(data.id);
|
||||
|
||||
jQuery('#title-@Html.FieldIdFor(m => m.ContentItemId)').text(data.displayText);
|
||||
|
||||
// define the menu text if it's empty
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Orchard.DynamicForms.Drivers {
|
||||
|
||||
var bindingsEditor = context.ShapeFactory.EditorTemplate(TemplateName: "FormBindings", Model: viewModel);
|
||||
|
||||
bindingsEditor.Metadata.Position = "Bindings:10";
|
||||
bindingsEditor.Metadata.Position = "Bindings:20";
|
||||
|
||||
return Editor(context, bindingsEditor);
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ namespace Orchard.DynamicForms.Drivers {
|
||||
_IsRequired: shape.Checkbox(
|
||||
Id: "IsMandatory",
|
||||
Name: "IsMandatory",
|
||||
Title: "Mandatory",
|
||||
Title: "Required",
|
||||
Value: "true",
|
||||
Description: T("Tick this checkbox to make this check box element mandatory.")),
|
||||
Description: T("Tick this checkbox to make this check box element required.")),
|
||||
_CustomValidationMessage: shape.Textbox(
|
||||
Id: "CustomValidationMessage",
|
||||
Name: "CustomValidationMessage",
|
||||
|
||||
@@ -82,7 +82,6 @@ namespace Orchard.DynamicForms.Drivers {
|
||||
Name: "HtmlEncode",
|
||||
Title: "Html Encode",
|
||||
Value: "true",
|
||||
Checked: true,
|
||||
Description: T("Check this option to automatically HTML encode submitted values to prevent code injection.")),
|
||||
_CreateContent: shape.Checkbox(
|
||||
Id: "CreateContent",
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Orchard.DynamicForms.Elements {
|
||||
}
|
||||
|
||||
public bool HtmlEncode {
|
||||
get { return this.Retrieve(x => x.HtmlEncode, () => true); }
|
||||
get { return this.Retrieve(x => x.HtmlEncode); }
|
||||
set { this.Store(x => x.HtmlEncode, value); }
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Orchard.DynamicForms.ValidationRules {
|
||||
|
||||
private LocalizedString GetValidationMessage(ValidationContext context) {
|
||||
return String.IsNullOrWhiteSpace(ErrorMessage)
|
||||
? T("{0} is a mandatory field.", context.FieldName)
|
||||
? T("{0} is a required field.", context.FieldName)
|
||||
: T(ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,12 @@ namespace Orchard.Layouts.Framework.Drivers {
|
||||
if (value != null) {
|
||||
context.Element.Data[name] = value.AttemptedValue;
|
||||
}
|
||||
else if (formElementShape.Metadata.Type == "Checkbox") {
|
||||
var shapeValue = formElementShape.Value as string;
|
||||
if (shapeValue != null && shapeValue.ToLower() == "true") {
|
||||
context.Element.Data[name] = "false";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace Orchard.Lists {
|
||||
builder.Describe("ListNavigation").OnDisplaying(context => {
|
||||
var containable = (ContainablePart) context.Shape.ContainablePart;
|
||||
var container = _containerService.Value.GetContainer(containable, VersionOptions.Latest);
|
||||
if (container == null) return;
|
||||
|
||||
var previous = _containerService.Value.Previous(container.Id, containable);
|
||||
var next = _containerService.Value.Next(container.Id, containable);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
@using Orchard.ContentManagement;
|
||||
@{
|
||||
Style.Require("jQueryColorBox");
|
||||
Style.Include("nprogress.css", "nprogress.min.css");
|
||||
Style.Include("nprogress.css");
|
||||
Style.Include("common-admin.css", "common-admin.min.css");
|
||||
Style.Include("list-admin.css", "list-admin.min.css");
|
||||
Script.Require("ContentPicker").AtFoot();
|
||||
|
||||
@@ -6,18 +6,21 @@ html.dyn #main ul.features button { display:none; }
|
||||
.features.detail-view .category > ul {
|
||||
border:1px solid #EAEAEA;
|
||||
margin-bottom:2em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.features.summary-view .category {
|
||||
overflow:hidden;
|
||||
padding-bottom:1em;
|
||||
}
|
||||
|
||||
.features.summary-view .category > ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.features.summary-view .feature {
|
||||
border:1px solid #EAEAEA;
|
||||
display:block;
|
||||
float:left;
|
||||
height:6em;
|
||||
margin:0 .5% 1% .5%;
|
||||
position:relative;
|
||||
width:32%;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,10 @@ namespace Orchard.MultiTenancy.Controllers {
|
||||
ModelState.AddModelError("Name", T("Invalid tenant name. Must contain characters only and no spaces.").Text);
|
||||
}
|
||||
|
||||
if (!string.Equals(viewModel.Name, "default", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace( viewModel.RequestUrlHost) && string.IsNullOrWhiteSpace(viewModel.RequestUrlPrefix)) {
|
||||
ModelState.AddModelError("RequestUrlHostRequestUrlPrefix", T("RequestUrlHost and RequestUrlPrefix can not be empty at the same time.").Text);
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid) {
|
||||
return View(viewModel);
|
||||
}
|
||||
@@ -143,6 +147,10 @@ namespace Orchard.MultiTenancy.Controllers {
|
||||
if (tenant == null)
|
||||
return HttpNotFound();
|
||||
|
||||
if (!string.Equals(viewModel.Name, "default", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(viewModel.RequestUrlHost) && string.IsNullOrWhiteSpace(viewModel.RequestUrlPrefix)) {
|
||||
ModelState.AddModelError("RequestUrlHostRequestUrlPrefix", T("RequestUrlHost and RequestUrlPrefix can not be empty at the same time.").Text);
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid) {
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ namespace Orchard.OutputCache.Filters {
|
||||
|
||||
// State.
|
||||
private CacheSettings _cacheSettings;
|
||||
private CacheRouteConfig _cacheRouteConfig;
|
||||
private DateTime _now;
|
||||
private WorkContext _workContext;
|
||||
private string _cacheKey;
|
||||
@@ -94,6 +95,13 @@ namespace Orchard.OutputCache.Filters {
|
||||
_now = _clock.UtcNow;
|
||||
_workContext = _workContextAccessor.GetContext();
|
||||
|
||||
var configurations = _cacheService.GetRouteConfigs();
|
||||
if (configurations.Any()) {
|
||||
var route = filterContext.Controller.ControllerContext.RouteData.Route;
|
||||
var key = _cacheService.GetRouteDescriptorKey(filterContext.HttpContext, route);
|
||||
_cacheRouteConfig = configurations.FirstOrDefault(c => c.RouteKey == key);
|
||||
}
|
||||
|
||||
if (!RequestIsCacheable(filterContext))
|
||||
return;
|
||||
|
||||
@@ -182,16 +190,8 @@ namespace Orchard.OutputCache.Filters {
|
||||
|
||||
Logger.Debug("Item '{0}' was rendered.", _cacheKey);
|
||||
|
||||
// Obtain individual route configuration, if any.
|
||||
CacheRouteConfig configuration = null;
|
||||
var configurations = _cacheService.GetRouteConfigs();
|
||||
if (configurations.Any()) {
|
||||
var route = filterContext.Controller.ControllerContext.RouteData.Route;
|
||||
var key = _cacheService.GetRouteDescriptorKey(filterContext.HttpContext, route);
|
||||
configuration = configurations.FirstOrDefault(c => c.RouteKey == key);
|
||||
}
|
||||
|
||||
if (!ResponseIsCacheable(filterContext, configuration)) {
|
||||
if (!ResponseIsCacheable(filterContext)) {
|
||||
filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
|
||||
filterContext.HttpContext.Response.Cache.SetNoStore();
|
||||
filterContext.HttpContext.Response.Cache.SetMaxAge(new TimeSpan(0));
|
||||
@@ -199,8 +199,8 @@ namespace Orchard.OutputCache.Filters {
|
||||
}
|
||||
|
||||
// Determine duration and grace time.
|
||||
var cacheDuration = configuration != null && configuration.Duration.HasValue ? configuration.Duration.Value : CacheSettings.DefaultCacheDuration;
|
||||
var cacheGraceTime = configuration != null && configuration.GraceTime.HasValue ? configuration.GraceTime.Value : CacheSettings.DefaultCacheGraceTime;
|
||||
var cacheDuration = _cacheRouteConfig != null && _cacheRouteConfig.Duration.HasValue ? _cacheRouteConfig.Duration.Value : CacheSettings.DefaultCacheDuration;
|
||||
var cacheGraceTime = _cacheRouteConfig != null && _cacheRouteConfig.GraceTime.HasValue ? _cacheRouteConfig.GraceTime.Value : CacheSettings.DefaultCacheGraceTime;
|
||||
|
||||
// Include each content item ID as tags for the cache entry.
|
||||
var contentItemIds = _displayedContentItemHandler.GetDisplayed().Select(x => x.ToString(CultureInfo.InvariantCulture)).ToArray();
|
||||
@@ -209,6 +209,15 @@ namespace Orchard.OutputCache.Filters {
|
||||
var response = filterContext.HttpContext.Response;
|
||||
var captureStream = new CaptureStream(response.Filter);
|
||||
response.Filter = captureStream;
|
||||
|
||||
// Add ETag header for the newly created item
|
||||
var etag = Guid.NewGuid().ToString("n");
|
||||
if (HttpRuntime.UsingIntegratedPipeline) {
|
||||
if (response.Headers.Get("ETag") == null) {
|
||||
response.Headers["ETag"] = etag;
|
||||
}
|
||||
}
|
||||
|
||||
captureStream.Captured += (output) => {
|
||||
try {
|
||||
// Since this is a callback any call to injected dependencies can result in an Autofac exception: "Instances
|
||||
@@ -229,12 +238,12 @@ namespace Orchard.OutputCache.Filters {
|
||||
Url = filterContext.HttpContext.Request.Url.AbsolutePath,
|
||||
Tenant = scope.Resolve<ShellSettings>().Name,
|
||||
StatusCode = response.StatusCode,
|
||||
Tags = new[] { _invariantCacheKey }.Union(contentItemIds).ToArray()
|
||||
Tags = new[] { _invariantCacheKey }.Union(contentItemIds).ToArray(),
|
||||
ETag = etag
|
||||
};
|
||||
|
||||
// Write the rendered item to the cache.
|
||||
var cacheStorageProvider = scope.Resolve<IOutputCacheStorageProvider>();
|
||||
cacheStorageProvider.Remove(_cacheKey);
|
||||
cacheStorageProvider.Set(_cacheKey, cacheItem);
|
||||
|
||||
Logger.Debug("Item '{0}' was written to cache.", _cacheKey);
|
||||
@@ -314,6 +323,12 @@ namespace Orchard.OutputCache.Filters {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't cache if individual route configuration says no.
|
||||
if (_cacheRouteConfig != null && _cacheRouteConfig.Duration == 0) {
|
||||
Logger.Debug("Request for item '{0}' ignored because route is configured to not be cached.", itemDescriptor);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore requests with the refresh key on the query string.
|
||||
foreach (var key in filterContext.RequestContext.HttpContext.Request.QueryString.AllKeys) {
|
||||
if (String.Equals(_refreshKey, key, StringComparison.OrdinalIgnoreCase)) {
|
||||
@@ -325,7 +340,7 @@ namespace Orchard.OutputCache.Filters {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool ResponseIsCacheable(ResultExecutedContext filterContext, CacheRouteConfig configuration) {
|
||||
protected virtual bool ResponseIsCacheable(ResultExecutedContext filterContext) {
|
||||
|
||||
if (filterContext.HttpContext.Request.Url == null) {
|
||||
return false;
|
||||
@@ -337,12 +352,6 @@ namespace Orchard.OutputCache.Filters {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't cache in individual route configuration says no.
|
||||
if (configuration != null && configuration.Duration == 0) {
|
||||
Logger.Debug("Response for item '{0}' will not be cached because route is configured to not be cached.", _cacheKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't cache if request created notifications.
|
||||
var hasNotifications = !String.IsNullOrEmpty(Convert.ToString(filterContext.Controller.TempData["messages"]));
|
||||
if (hasNotifications) {
|
||||
@@ -467,6 +476,7 @@ namespace Orchard.OutputCache.Filters {
|
||||
|
||||
private void ServeCachedItem(ActionExecutingContext filterContext, CacheItem cacheItem) {
|
||||
var response = filterContext.HttpContext.Response;
|
||||
var request = filterContext.HttpContext.Request;
|
||||
|
||||
// Fix for missing charset in response headers
|
||||
response.Charset = response.Charset;
|
||||
@@ -479,9 +489,24 @@ namespace Orchard.OutputCache.Filters {
|
||||
|
||||
// Shorcut action execution.
|
||||
filterContext.Result = new FileContentResult(cacheItem.Output, cacheItem.ContentType);
|
||||
|
||||
response.StatusCode = cacheItem.StatusCode;
|
||||
|
||||
// Add ETag header
|
||||
if (HttpRuntime.UsingIntegratedPipeline && response.Headers.Get("ETag") == null) {
|
||||
response.Headers["ETag"] = cacheItem.ETag;
|
||||
}
|
||||
|
||||
// Check ETag in request
|
||||
// https://www.w3.org/2005/MWI/BPWG/techs/CachingWithETag.html
|
||||
var etag = request.Headers["If-None-Match"];
|
||||
if (!String.IsNullOrEmpty(etag)) {
|
||||
if (String.Equals(etag, cacheItem.ETag, StringComparison.Ordinal)) {
|
||||
// ETag matches the cached item, we return a 304
|
||||
filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.NotModified);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ApplyCacheControl(response);
|
||||
}
|
||||
|
||||
@@ -511,15 +536,6 @@ namespace Orchard.OutputCache.Filters {
|
||||
// response.DisableUserCache();
|
||||
// response.DisableKernelCache();
|
||||
|
||||
// An ETag is a string that uniquely identifies a specific version of a component.
|
||||
// We use the cache item to detect if it's a new one.
|
||||
if (HttpRuntime.UsingIntegratedPipeline) {
|
||||
if (response.Headers.Get("ETag") == null) {
|
||||
// What is the point of GetHashCode() of a newly generated item? /DanielStolt
|
||||
response.Cache.SetETag(new CacheItem().GetHashCode().ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
if (CacheSettings.VaryByQueryStringParameters == null) {
|
||||
response.Cache.VaryByParams["*"] = true;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Orchard.OutputCache.Models {
|
||||
[Serializable]
|
||||
public class CacheItem {
|
||||
// used for serialization compatibility
|
||||
public static readonly string Version = "1";
|
||||
public static readonly string Version = "2";
|
||||
|
||||
public DateTime CachedOnUtc { get; set; }
|
||||
public int Duration { get; set; }
|
||||
@@ -18,6 +18,7 @@ namespace Orchard.OutputCache.Models {
|
||||
public string Tenant { get; set; }
|
||||
public int StatusCode { get; set; }
|
||||
public string[] Tags { get; set; }
|
||||
public string ETag { get; set; }
|
||||
|
||||
public int ValidFor {
|
||||
get { return Duration; }
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Orchard.OutputCache.Services {
|
||||
}
|
||||
|
||||
public void Set(string key, CacheItem cacheItem) {
|
||||
_workContext.HttpContext.Cache.Remove(key);
|
||||
_workContext.HttpContext.Cache.Add(
|
||||
key,
|
||||
cacheItem,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
float:left;
|
||||
}
|
||||
.extensionName.installed {
|
||||
background: url("images/installed.gif") no-repeat 0px 8px #fff;
|
||||
background: url("images/installed.gif") no-repeat 0px 8px;
|
||||
padding:0 0 0 60px;
|
||||
}
|
||||
.contentItems .related {
|
||||
|
||||
@@ -251,7 +251,7 @@ namespace Orchard.Projections.FilterEditors.Forms {
|
||||
case DateTimeOperator.LessThan:
|
||||
return T("{0} is less than {1}{2}", fieldName, value, T(valueUnit));
|
||||
case DateTimeOperator.LessThanEquals:
|
||||
return T("{0} is less or equal than {1}{2}", fieldName, value, T(valueUnit));
|
||||
return T("{0} is less than or equal to {1}{2}", fieldName, value, T(valueUnit));
|
||||
case DateTimeOperator.Equals:
|
||||
return T("{0} equals {1}{2}", fieldName, value, T(valueUnit));
|
||||
case DateTimeOperator.NotEquals:
|
||||
@@ -259,7 +259,7 @@ namespace Orchard.Projections.FilterEditors.Forms {
|
||||
case DateTimeOperator.GreaterThan:
|
||||
return T("{0} is greater than {1}{2}", fieldName, value, T(valueUnit));
|
||||
case DateTimeOperator.GreaterThanEquals:
|
||||
return T("{0} is greater or equal than {1}{2}", fieldName, value, T(valueUnit));
|
||||
return T("{0} is greater than or equal to {1}{2}", fieldName, value, T(valueUnit));
|
||||
case DateTimeOperator.Between:
|
||||
return T("{0} is between {1}{2} and {3}{4}", fieldName, min, T(minUnit), max, T(maxUnit));
|
||||
case DateTimeOperator.NotBetween:
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace Orchard.Projections.FilterEditors.Forms {
|
||||
case NumericOperator.LessThan:
|
||||
return T("{0} is less than {1}", fieldName, value);
|
||||
case NumericOperator.LessThanEquals:
|
||||
return T("{0} is less or equal than {1}", fieldName, value);
|
||||
return T("{0} is less than or equal to {1}", fieldName, value);
|
||||
case NumericOperator.Equals:
|
||||
return T("{0} equals {1}", fieldName, value);
|
||||
case NumericOperator.NotEquals:
|
||||
@@ -136,7 +136,7 @@ namespace Orchard.Projections.FilterEditors.Forms {
|
||||
case NumericOperator.GreaterThan:
|
||||
return T("{0} is greater than {1}", fieldName, value);
|
||||
case NumericOperator.GreaterThanEquals:
|
||||
return T("{0} is greater or equal than {1}", fieldName, value);
|
||||
return T("{0} is greater than or equal to {1}", fieldName, value);
|
||||
case NumericOperator.Between:
|
||||
return T("{0} is between {1} and {2}", fieldName, min, max);
|
||||
case NumericOperator.NotBetween:
|
||||
|
||||
@@ -822,6 +822,8 @@
|
||||
<Content Include="Styles\jquery-colorbox.min.css" />
|
||||
<Content Include="Styles\jquery-datetime-editor.css" />
|
||||
<Content Include="Styles\jquery-datetime-editor.min.css" />
|
||||
<Content Include="Styles\jquery-ui.css" />
|
||||
<Content Include="Styles\jquery-ui.min.css" />
|
||||
<Content Include="Styles\TimeEntry\jquery.timeentry.css" />
|
||||
<Content Include="Styles\TimeEntry\jquery.timeentry.min.css" />
|
||||
<Content Include="Web.config" />
|
||||
|
||||
@@ -52,18 +52,18 @@ namespace Orchard.Resources {
|
||||
manifest.DefineStyle("jQueryCalendars_Picker").SetUrl("Calendars/jquery.calendars.picker.full.min.css", "Calendars/jquery.calendars.picker.full.css").SetDependencies("jQueryUI_Orchard").SetVersion("2.0.1");
|
||||
|
||||
// jQuery Time Entry.
|
||||
manifest.DefineScript("jQueryTimeEntry").SetUrl("TimeEntry/timejquery.timeentry.min.js", "TimeEntry/jquery.timeentry.js").SetDependencies("jQueryPlugin").SetVersion("2.0.1");
|
||||
manifest.DefineStyle("jQueryTimeEntry").SetUrl("TimeEntry/jquery.timeentry.css").SetVersion("2.0.1");
|
||||
manifest.DefineScript("jQueryTimeEntry").SetUrl("TimeEntry/jquery.timeentry.min.js", "TimeEntry/jquery.timeentry.js").SetDependencies("jQueryPlugin").SetVersion("2.0.1");
|
||||
manifest.DefineStyle("jQueryTimeEntry").SetUrl("TimeEntry/jquery.timeentry.min.css","TimeEntry/jquery.timeentry.css").SetVersion("2.0.1");
|
||||
|
||||
// jQuery Date/Time Editor Enhancements.
|
||||
manifest.DefineStyle("jQueryDateTimeEditor").SetUrl("jquery-datetime-editor.css").SetDependencies("DateTimeEditor");
|
||||
manifest.DefineStyle("jQueryDateTimeEditor").SetUrl("jquery-datetime-editor.min.css","jquery-datetime-editor.css").SetDependencies("DateTimeEditor");
|
||||
|
||||
// jQuery File Upload.
|
||||
manifest.DefineScript("jQueryFileUpload").SetUrl("jquery.fileupload-full.min.js", "jquery.fileupload-full.js").SetVersion("9.11.2").SetDependencies("jQueryUI_Widget");
|
||||
|
||||
// jQuery Color Box.
|
||||
manifest.DefineScript("jQueryColorBox").SetUrl("jquery.colorbox.min.js", "jquery.colorbox.js").SetVersion("1.6.3").SetDependencies("jQuery");
|
||||
manifest.DefineStyle("jQueryColorBox").SetUrl("jquery.colorbox.min.css", "jquery.colorbox.min.css").SetVersion("1.6.3");
|
||||
manifest.DefineStyle("jQueryColorBox").SetUrl("jquery.colorbox.min.css", "jquery.colorbox.css").SetVersion("1.6.3");
|
||||
|
||||
// jQuery Cookie.
|
||||
manifest.DefineScript("jQueryCookie").SetUrl("jquery.cookie.min.js", "jquery.cookie.js").SetVersion("1.4.1").SetDependencies("jQuery");
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace Orchard.Taxonomies.Controllers {
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult Delete(int id) {
|
||||
if (!Services.Authorizer.Authorize(Permissions.CreateTaxonomy, T("Couldn't delete taxonomy")))
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageTaxonomies, T("Couldn't delete taxonomy")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
var taxonomy = _taxonomyService.GetTaxonomy(id);
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Web.Mvc;
|
||||
using Orchard.Taxonomies.Models;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Mvc.Html;
|
||||
using Orchard.Taxonomies.ViewModels;
|
||||
using Orchard.Taxonomies.Services;
|
||||
using Orchard.UI.Admin;
|
||||
@@ -70,15 +71,24 @@ namespace Orchard.Taxonomies.Controllers {
|
||||
var checkedEntries = viewModel.Terms.Where(t => t.IsChecked).ToList();
|
||||
switch (viewModel.BulkAction) {
|
||||
case TermsAdminIndexBulkAction.None:
|
||||
Services.Notifier.Information(T("No action selected."));
|
||||
break;
|
||||
case TermsAdminIndexBulkAction.Delete:
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageTerms, T("Couldn't delete term")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
if(!checkedEntries.Any()) {
|
||||
Services.Notifier.Information(T("No terms selected."));
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var entry in checkedEntries) {
|
||||
var term = _taxonomyService.GetTerm(entry.Id);
|
||||
_taxonomyService.DeleteTerm(term);
|
||||
}
|
||||
|
||||
Services.Notifier.Information(T.Plural("{0} term has been removed.", "{0} terms have been removed.", checkedEntries.Count));
|
||||
|
||||
break;
|
||||
case TermsAdminIndexBulkAction.Merge:
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageTerms, T("Couldn't delete term")))
|
||||
@@ -100,8 +110,6 @@ namespace Orchard.Taxonomies.Controllers {
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
Services.Notifier.Success(T("{0} term have been removed.", checkedEntries.Count));
|
||||
|
||||
return RedirectToAction("Index", new { taxonomyId = viewModel.TaxonomyId });
|
||||
}
|
||||
|
||||
@@ -207,7 +215,7 @@ namespace Orchard.Taxonomies.Controllers {
|
||||
|
||||
public ActionResult Edit(int id) {
|
||||
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageTerms, T("Not allowed to manage taxonomies")))
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageTerms, T("Not allowed to manage terms")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
var term = _taxonomyService.GetTerm(id);
|
||||
@@ -220,7 +228,7 @@ namespace Orchard.Taxonomies.Controllers {
|
||||
|
||||
[HttpPost, ActionName("Edit")]
|
||||
public ActionResult EditPost(int id) {
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageTaxonomies, T("Couldn't edit taxonomy")))
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageTerms, T("Couldn't edit term")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
var term = _taxonomyService.GetTerm(id);
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
|
||||
@if (!Model.HasTerms && AuthorizedFor(Orchard.Taxonomies.Permissions.CreateTerm)) {
|
||||
<div class="no-terms">
|
||||
@T("There are no terms defined for {0} yet.", Model.DisplayName.CamelFriendly())
|
||||
@T("There are no terms defined for {0} yet.", Model.DisplayName)
|
||||
<a href="@Url.Action("Index", "TermAdmin", new { taxonomyId = Model.TaxonomyId, area = "Orchard.Taxonomies" })">@T("Create some terms")</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Drivers;
|
||||
using Orchard.Core.Title.Models;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Templates.Models;
|
||||
using System.Linq;
|
||||
|
||||
namespace Orchard.Templates.Drivers {
|
||||
public class TitlePartDriver : ContentPartDriver<TitlePart> {
|
||||
private readonly IContentManager _contentManager;
|
||||
|
||||
public Localizer T { get; set; }
|
||||
|
||||
public TitlePartDriver(IContentManager contentManager) {
|
||||
_contentManager = contentManager;
|
||||
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
protected override DriverResult Editor(TitlePart part, IUpdateModel updater, dynamic shapeHelper) {
|
||||
if (!part.ContentItem.Has<ShapePart>()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
updater.TryUpdateModel(part, Prefix, null, null);
|
||||
|
||||
// We need to query for the content type names because querying for content parts has no effect on the database side.
|
||||
var contentTypesWithShapePart = _contentManager
|
||||
.GetContentTypeDefinitions()
|
||||
.Where(typeDefinition => typeDefinition.Parts.Any(partDefinition => partDefinition.PartDefinition.Name == "ShapePart"))
|
||||
.Select(typeDefinition => typeDefinition.Name);
|
||||
|
||||
// If ShapePart is only dynamically added to this content type or even this content item then we won't find
|
||||
// a corresponding content type definition, so using the current content type too.
|
||||
contentTypesWithShapePart = contentTypesWithShapePart.Union(new[] { part.ContentItem.ContentType });
|
||||
|
||||
var existingShapeCount = _contentManager
|
||||
.Query(VersionOptions.Latest, contentTypesWithShapePart.ToArray())
|
||||
.Where<TitlePartRecord>(record => record.Title == part.Title && record.ContentItemRecord.Id != part.ContentItem.Id)
|
||||
.Count();
|
||||
|
||||
if (existingShapeCount > 0) {
|
||||
updater.AddModelError("ShapeNameAlreadyExists", T("A template with the given name already exists."));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,6 +180,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="AdminMenu.cs" />
|
||||
<Compile Include="Controllers\AdminController.cs" />
|
||||
<Compile Include="Drivers\TitlePartDriver.cs" />
|
||||
<Compile Include="Helpers\StringExtensions.cs" />
|
||||
<Compile Include="Handlers\ShapePartHandler.cs" />
|
||||
<Compile Include="Drivers\ShapePartDriver.cs" />
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Orchard.Users.Controllers {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!String.IsNullOrWhiteSpace(options.Search)) {
|
||||
if(!string.IsNullOrWhiteSpace(options.Search)) {
|
||||
users = users.Where(u => u.UserName.Contains(options.Search) || u.Email.Contains(options.Search));
|
||||
}
|
||||
|
||||
@@ -219,6 +219,10 @@ namespace Orchard.Users.Controllers {
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
var user = Services.ContentManager.Get<UserPart>(id);
|
||||
|
||||
if (user == null)
|
||||
return HttpNotFound();
|
||||
|
||||
var editor = Shape.EditorTemplate(TemplateName: "Parts/User.Edit", Model: new UserEditViewModel {User = user}, Prefix: null);
|
||||
editor.Metadata.Position = "2";
|
||||
var model = Services.ContentManager.BuildEditor(user);
|
||||
@@ -233,11 +237,15 @@ namespace Orchard.Users.Controllers {
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
var user = Services.ContentManager.Get<UserPart>(id, VersionOptions.DraftRequired);
|
||||
|
||||
if (user == null)
|
||||
return HttpNotFound();
|
||||
|
||||
string previousName = user.UserName;
|
||||
|
||||
var model = Services.ContentManager.UpdateEditor(user, this);
|
||||
|
||||
var editModel = new UserEditViewModel {User = user};
|
||||
var editModel = new UserEditViewModel { User = user };
|
||||
if (TryUpdateModel(editModel)) {
|
||||
if (!_userService.VerifyUserUnicity(id, editModel.UserName, editModel.Email)) {
|
||||
AddModelError("NotUniqueUserName", T("User with that username and/or email already exists."));
|
||||
@@ -248,7 +256,7 @@ namespace Orchard.Users.Controllers {
|
||||
}
|
||||
else {
|
||||
// also update the Super user if this is the renamed account
|
||||
if (String.Equals(Services.WorkContext.CurrentSite.SuperUser, previousName, StringComparison.Ordinal)) {
|
||||
if (string.Equals(Services.WorkContext.CurrentSite.SuperUser, previousName, StringComparison.Ordinal)) {
|
||||
_siteService.GetSiteSettings().As<SiteSettingsPart>().SuperUser = editModel.UserName;
|
||||
}
|
||||
|
||||
@@ -279,17 +287,18 @@ namespace Orchard.Users.Controllers {
|
||||
|
||||
var user = Services.ContentManager.Get<IUser>(id);
|
||||
|
||||
if (user != null) {
|
||||
if (String.Equals(Services.WorkContext.CurrentSite.SuperUser, user.UserName, StringComparison.Ordinal)) {
|
||||
Services.Notifier.Error(T("The Super user can't be removed. Please disable this account or specify another Super user account."));
|
||||
}
|
||||
else if (String.Equals(Services.WorkContext.CurrentUser.UserName, user.UserName, StringComparison.Ordinal)) {
|
||||
Services.Notifier.Error(T("You can't remove your own account. Please log in with another account."));
|
||||
}
|
||||
else{
|
||||
Services.ContentManager.Remove(user.ContentItem);
|
||||
if (user == null)
|
||||
return HttpNotFound();
|
||||
|
||||
if (string.Equals(Services.WorkContext.CurrentSite.SuperUser, user.UserName, StringComparison.Ordinal)) {
|
||||
Services.Notifier.Error(T("The Super user can't be removed. Please disable this account or specify another Super user account."));
|
||||
}
|
||||
else if (string.Equals(Services.WorkContext.CurrentUser.UserName, user.UserName, StringComparison.Ordinal)) {
|
||||
Services.Notifier.Error(T("You can't remove your own account. Please log in with another account."));
|
||||
}
|
||||
else {
|
||||
Services.ContentManager.Remove(user.ContentItem);
|
||||
Services.Notifier.Success(T("User {0} deleted", user.UserName));
|
||||
}
|
||||
}
|
||||
|
||||
return RedirectToAction("Index");
|
||||
@@ -302,16 +311,18 @@ namespace Orchard.Users.Controllers {
|
||||
|
||||
var user = Services.ContentManager.Get<IUser>(id);
|
||||
|
||||
if ( user != null ) {
|
||||
var siteUrl = Services.WorkContext.CurrentSite.BaseUrl;
|
||||
if (String.IsNullOrWhiteSpace(siteUrl)) {
|
||||
siteUrl = HttpContext.Request.ToRootUrlString();
|
||||
}
|
||||
if (user == null)
|
||||
return HttpNotFound();
|
||||
|
||||
_userService.SendChallengeEmail(user.As<UserPart>(), nonce => Url.MakeAbsolute(Url.Action("ChallengeEmail", "Account", new { Area = "Orchard.Users", nonce = nonce }), siteUrl));
|
||||
Services.Notifier.Success(T("Challenge email sent to {0}", user.UserName));
|
||||
var siteUrl = Services.WorkContext.CurrentSite.BaseUrl;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(siteUrl)) {
|
||||
siteUrl = HttpContext.Request.ToRootUrlString();
|
||||
}
|
||||
|
||||
_userService.SendChallengeEmail(user.As<UserPart>(), nonce => Url.MakeAbsolute(Url.Action("ChallengeEmail", "Account", new { Area = "Orchard.Users", nonce = nonce }), siteUrl));
|
||||
Services.Notifier.Success(T("Challenge email sent to {0}", user.UserName));
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
@@ -322,11 +333,12 @@ namespace Orchard.Users.Controllers {
|
||||
|
||||
var user = Services.ContentManager.Get<IUser>(id);
|
||||
|
||||
if ( user != null ) {
|
||||
user.As<UserPart>().RegistrationStatus = UserStatus.Approved;
|
||||
if (user == null)
|
||||
return HttpNotFound();
|
||||
|
||||
user.As<UserPart>().RegistrationStatus = UserStatus.Approved;
|
||||
Services.Notifier.Success(T("User {0} approved", user.UserName));
|
||||
_userEventHandlers.Approved(user);
|
||||
}
|
||||
_userEventHandlers.Approved(user);
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
@@ -338,14 +350,15 @@ namespace Orchard.Users.Controllers {
|
||||
|
||||
var user = Services.ContentManager.Get<IUser>(id);
|
||||
|
||||
if (user != null) {
|
||||
if (String.Equals(Services.WorkContext.CurrentUser.UserName, user.UserName, StringComparison.Ordinal)) {
|
||||
Services.Notifier.Error(T("You can't disable your own account. Please log in with another account"));
|
||||
}
|
||||
else {
|
||||
user.As<UserPart>().RegistrationStatus = UserStatus.Pending;
|
||||
if (user == null)
|
||||
return HttpNotFound();
|
||||
|
||||
if (string.Equals(Services.WorkContext.CurrentUser.UserName, user.UserName, StringComparison.Ordinal)) {
|
||||
Services.Notifier.Error(T("You can't disable your own account. Please log in with another account"));
|
||||
}
|
||||
else {
|
||||
user.As<UserPart>().RegistrationStatus = UserStatus.Pending;
|
||||
Services.Notifier.Success(T("User {0} disabled", user.UserName));
|
||||
}
|
||||
}
|
||||
|
||||
return RedirectToAction("Index");
|
||||
|
||||
@@ -4,9 +4,11 @@ using System.Linq;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Data;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Exceptions;
|
||||
using Orchard.Forms.Services;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Localization.Services;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Services;
|
||||
using Orchard.Tasks;
|
||||
using Orchard.Workflows.Models;
|
||||
@@ -122,19 +124,29 @@ namespace Orchard.Workflows.Activities {
|
||||
_contentManager = contentManager;
|
||||
_workflowManager = workflowManager;
|
||||
_awaitingActivityRepository = awaitingActivityRepository;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
public void Sweep() {
|
||||
var awaiting = _awaitingActivityRepository.Table.Where(x => x.ActivityRecord.Name == "Timer").ToList();
|
||||
|
||||
|
||||
foreach (var action in awaiting) {
|
||||
var contentItem = _contentManager.Get(action.WorkflowRecord.ContentItemRecord.Id, VersionOptions.Latest);
|
||||
var tokens = new Dictionary<string, object> { { "Content", contentItem } };
|
||||
var workflowState = FormParametersHelper.FromJsonString(action.WorkflowRecord.State);
|
||||
workflowState.TimerActivity_StartedUtc = null;
|
||||
action.WorkflowRecord.State = FormParametersHelper.ToJsonString(workflowState);
|
||||
_workflowManager.TriggerEvent("Timer", contentItem, () => tokens);
|
||||
try {
|
||||
var contentItem = action.WorkflowRecord.ContentItemRecord != null ? _contentManager.Get(action.WorkflowRecord.ContentItemRecord.Id, VersionOptions.Latest) : null;
|
||||
var tokens = new Dictionary<string, object> { { "Content", contentItem } };
|
||||
var workflowState = FormParametersHelper.FromJsonString(action.WorkflowRecord.State);
|
||||
workflowState.TimerActivity_StartedUtc = null;
|
||||
action.WorkflowRecord.State = FormParametersHelper.ToJsonString(workflowState);
|
||||
_workflowManager.TriggerEvent("Timer", contentItem, () => tokens);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (ex.IsFatal()) {
|
||||
throw;
|
||||
}
|
||||
Logger.Error(ex, "TimerBackgroundTask: Error while processing background task \"{0}\".", action.ActivityRecord.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,10 +226,6 @@
|
||||
<assemblyIdentity name="Iesi.Collections" publicKeyToken="aa95f207798dfdb4" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
|
||||
@@ -60,12 +60,18 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeAttributeStrategy {
|
||||
var output = new HtmlStringWriter();
|
||||
var arguments = methodInfo.GetParameters()
|
||||
.Select(parameter => BindParameter(displayContext, parameter, output));
|
||||
|
||||
var returnValue = methodInfo.Invoke(serviceInstance, arguments.ToArray());
|
||||
if (methodInfo.ReturnType != typeof(void)) {
|
||||
output.Write(CoerceHtmlString(returnValue));
|
||||
try {
|
||||
var returnValue = methodInfo.Invoke(serviceInstance, arguments.ToArray());
|
||||
if (methodInfo.ReturnType != typeof(void)) {
|
||||
output.Write(CoerceHtmlString(returnValue));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
catch(TargetInvocationException e) {
|
||||
// Throwing a TIE here will probably kill the web process
|
||||
// in Azure. For unknown reasons.
|
||||
throw e.InnerException;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
private static IHtmlString CoerceHtmlString(object invoke) {
|
||||
|
||||
@@ -11,7 +11,6 @@ using Orchard.DisplayManagement.Shapes;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Mvc;
|
||||
using Orchard.Mvc.Extensions;
|
||||
|
||||
namespace Orchard.DisplayManagement.Implementation {
|
||||
public class DefaultDisplayManager : IDisplayManager {
|
||||
@@ -62,7 +61,9 @@ namespace Orchard.DisplayManagement.Implementation {
|
||||
return CoerceHtmlString(context.Value);
|
||||
|
||||
var workContext = _workContextAccessor.GetContext();
|
||||
var shapeTable = !_httpContextAccessor.Current().IsBackgroundContext()
|
||||
// CurrentTheme is now available in the background, so here we no longer use IsBackgroundContext().
|
||||
// We only do a null check, so we can render in the background a view that only exists in the theme.
|
||||
var shapeTable = _httpContextAccessor.Current() != null
|
||||
? _shapeTableLocator.Value.Lookup(workContext.CurrentTheme.Id)
|
||||
: _shapeTableLocator.Value.Lookup(null);
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ using Orchard.Logging;
|
||||
using Orchard.Mvc;
|
||||
using Orchard.Mvc.Extensions;
|
||||
using Orchard.Utility.Extensions;
|
||||
using Orchard.Exceptions;
|
||||
using Orchard.Utility;
|
||||
using System.Threading;
|
||||
|
||||
namespace Orchard.Environment {
|
||||
// All the event handlers that DefaultOrchardHost implements have to be declared in OrchardStarter.
|
||||
@@ -30,10 +31,14 @@ namespace Orchard.Environment {
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly static object _syncLock = new object();
|
||||
private readonly static object _shellContextsWriteLock = new object();
|
||||
private readonly NamedReaderWriterLock _shellActivationLock = new NamedReaderWriterLock();
|
||||
|
||||
private IEnumerable<ShellContext> _shellContexts;
|
||||
private readonly ContextState<IList<ShellSettings>> _tenantsToRestart;
|
||||
|
||||
public int Retries { get; set; }
|
||||
public bool DelayRetries { get; set; }
|
||||
|
||||
public DefaultOrchardHost(
|
||||
IShellSettingsManager shellSettingsManager,
|
||||
IShellContextFactory shellContextFactory,
|
||||
@@ -142,16 +147,34 @@ namespace Orchard.Environment {
|
||||
// Load all tenants, and activate their shell.
|
||||
if (allSettings.Any()) {
|
||||
Parallel.ForEach(allSettings, settings => {
|
||||
try {
|
||||
var context = CreateShellContext(settings);
|
||||
ActivateShell(context);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (ex.IsFatal()) {
|
||||
throw;
|
||||
for (var i = 0; i <= Retries; i++) {
|
||||
|
||||
// Not the first attempt, wait for a while ...
|
||||
if (DelayRetries && i > 0) {
|
||||
|
||||
// Wait for i^2 which means 1, 2, 4, 8 ... seconds
|
||||
Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(i, 2)));
|
||||
}
|
||||
Logger.Error(ex, "A tenant could not be started: " + settings.Name);
|
||||
|
||||
try {
|
||||
var context = CreateShellContext(settings);
|
||||
ActivateShell(context);
|
||||
|
||||
// If everything went well, return to stop the retry loop
|
||||
return;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (i == Retries) {
|
||||
Logger.Fatal("A tenant could not be started: {0} after {1} retries.", settings.Name, Retries);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
Logger.Error(ex, "A tenant could not be started: " + settings.Name + " Attempt number: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
while (_processingEngine.AreTasksPending()) {
|
||||
Logger.Debug("Processing pending task after activate Shell");
|
||||
_processingEngine.ExecuteNextTask();
|
||||
@@ -245,10 +268,30 @@ namespace Orchard.Environment {
|
||||
protected virtual void BeginRequest() {
|
||||
BlockRequestsDuringSetup();
|
||||
|
||||
// Ensure all shell contexts are loaded, or need to be reloaded if
|
||||
// extensions have changed
|
||||
MonitorExtensions();
|
||||
BuildCurrent();
|
||||
Action ensureInitialized = () => {
|
||||
// Ensure all shell contexts are loaded, or need to be reloaded if
|
||||
// extensions have changed
|
||||
MonitorExtensions();
|
||||
BuildCurrent();
|
||||
};
|
||||
|
||||
ShellSettings currentShellSettings = null;
|
||||
|
||||
var httpContext = _httpContextAccessor.Current();
|
||||
if (httpContext != null) {
|
||||
currentShellSettings = _runningShellTable.Match(httpContext);
|
||||
}
|
||||
|
||||
if (currentShellSettings == null) {
|
||||
ensureInitialized();
|
||||
}
|
||||
else {
|
||||
_shellActivationLock.RunWithReadLock(currentShellSettings.Name, () => {
|
||||
ensureInitialized();
|
||||
});
|
||||
}
|
||||
|
||||
// StartUpdatedShells can cause a writer shell activation lock so it should run outside the reader lock.
|
||||
StartUpdatedShells();
|
||||
}
|
||||
|
||||
@@ -306,19 +349,21 @@ namespace Orchard.Environment {
|
||||
}
|
||||
// reload the shell as its settings have changed
|
||||
else {
|
||||
// dispose previous context
|
||||
shellContext.Shell.Terminate();
|
||||
_shellActivationLock.RunWithWriteLock(settings.Name, () => {
|
||||
// dispose previous context
|
||||
shellContext.Shell.Terminate();
|
||||
|
||||
var context = _shellContextFactory.CreateShellContext(settings);
|
||||
var context = _shellContextFactory.CreateShellContext(settings);
|
||||
|
||||
// Activate and register modified context.
|
||||
// Forcing enumeration with ToArray() so a lazy execution isn't causing issues by accessing the disposed shell context.
|
||||
_shellContexts = _shellContexts.Where(shell => shell.Settings.Name != settings.Name).Union(new[] { context }).ToArray();
|
||||
// Activate and register modified context.
|
||||
// Forcing enumeration with ToArray() so a lazy execution isn't causing issues by accessing the disposed shell context.
|
||||
_shellContexts = _shellContexts.Where(shell => shell.Settings.Name != settings.Name).Union(new[] { context }).ToArray();
|
||||
|
||||
shellContext.Dispose();
|
||||
context.Shell.Activate();
|
||||
shellContext.Dispose();
|
||||
context.Shell.Activate();
|
||||
|
||||
_runningShellTable.Update(settings);
|
||||
_runningShellTable.Update(settings);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace Orchard.Environment {
|
||||
throw;
|
||||
}
|
||||
|
||||
Logger.Error(ex, "An unexcepted error occured while terminating the Shell");
|
||||
Logger.Error(ex, "An unexpected error occured while terminating the Shell");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,6 +699,7 @@
|
||||
<Compile Include="Messaging\Services\IMessagingChannel.cs" />
|
||||
<Compile Include="IWorkContextAccessor.cs" />
|
||||
<Compile Include="Utility\Extensions\VirtualPathProviderExtensions.cs" />
|
||||
<Compile Include="Utility\NamedReaderWriterLock.cs" />
|
||||
<Compile Include="Utility\ReflectionHelper.cs" />
|
||||
<Compile Include="Validation\PathValidation.cs" />
|
||||
<Compile Include="Wcf\OrchardDependencyInjectionServiceBehavior.cs" />
|
||||
|
||||
84
src/Orchard/Utility/NamedReaderWriterLock.cs
Normal file
84
src/Orchard/Utility/NamedReaderWriterLock.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Orchard.Utility {
|
||||
/// <summary>
|
||||
/// Provides locking similar to <see cref="ReaderWriterLockSlim"/> but
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Taken from http://johnculviner.com/achieving-named-lock-locker-functionality-in-c-4-0/ and adapted a bit. Namely:
|
||||
/// - <see cref="GetOrAdd"/> uses a new ReaderWriterLockSlim to overcome possible concurrency issues where the factory delegate could run multiple times.
|
||||
/// - Implemented <see cref="IDisposable"/>.
|
||||
/// </remarks>
|
||||
public class NamedReaderWriterLock : IDisposable {
|
||||
private readonly ConcurrentDictionary<string, ReaderWriterLockSlim> _lockDictonary = new ConcurrentDictionary<string, ReaderWriterLockSlim>();
|
||||
|
||||
public ReaderWriterLockSlim GetLock(string name) {
|
||||
return _lockDictonary.GetOrAdd(name, new ReaderWriterLockSlim());
|
||||
}
|
||||
|
||||
public TResult RunWithReadLock<TResult>(string name, Func<TResult> body) {
|
||||
var rwLock = GetLock(name);
|
||||
try {
|
||||
rwLock.EnterReadLock();
|
||||
return body();
|
||||
}
|
||||
finally {
|
||||
rwLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void RunWithReadLock(string name, Action body) {
|
||||
var rwLock = GetLock(name);
|
||||
try {
|
||||
rwLock.EnterReadLock();
|
||||
body();
|
||||
}
|
||||
finally {
|
||||
rwLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public TResult RunWithWriteLock<TResult>(string name, Func<TResult> body) {
|
||||
var rwLock = GetLock(name);
|
||||
try {
|
||||
rwLock.EnterWriteLock();
|
||||
return body();
|
||||
}
|
||||
finally {
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void RunWithWriteLock(string name, Action body) {
|
||||
var rwLock = GetLock(name);
|
||||
try {
|
||||
rwLock.EnterWriteLock();
|
||||
body();
|
||||
}
|
||||
finally {
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveLock(string name) {
|
||||
ReaderWriterLockSlim o;
|
||||
_lockDictonary.TryRemove(name, out o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes all the internal <see cref="ReaderWriterLockSlim"/> objects. Only call this if you're sure that no concurrent code executes
|
||||
/// any other instance method of this class!
|
||||
/// </summary>
|
||||
public void Dispose() {
|
||||
foreach (var lockSlim in _lockDictonary.Values) {
|
||||
lockSlim.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,6 +130,7 @@
|
||||
<PropertyValue name="AutoIndentOnTab">false</PropertyValue>
|
||||
<PropertyValue name="ColorizeInactiveBlocksDifferently">true</PropertyValue>
|
||||
<PropertyValue name="DisableBrowsingUpToDateCheck">true</PropertyValue>
|
||||
<PropertyValue name="DisableCreateDeclDefnScan">false</PropertyValue>
|
||||
<PropertyValue name="DisableErrorReporting">false</PropertyValue>
|
||||
<PropertyValue name="DisableIntelliSenseErrorsInErrorList">false</PropertyValue>
|
||||
<PropertyValue name="EnableChangeSignature">false</PropertyValue>
|
||||
@@ -154,6 +155,7 @@
|
||||
<PropertyValue name="IndentPreprocessor">2</PropertyValue>
|
||||
<PropertyValue name="IndentationReference">2</PropertyValue>
|
||||
<PropertyValue name="MemberListDotToArrow">false</PropertyValue>
|
||||
<PropertyValue name="MemberListFilterHeuristic">false</PropertyValue>
|
||||
<PropertyValue name="NewlineControlBlockBrace">0</PropertyValue>
|
||||
<PropertyValue name="NewlineEmptyFunctionCloseBrace">true</PropertyValue>
|
||||
<PropertyValue name="NewlineEmptyTypeCloseBrace">true</PropertyValue>
|
||||
@@ -235,6 +237,7 @@
|
||||
<PropertyValue name="ShowVirtualProjectsInSolutionExplorerWhenSolutionOpen">false</PropertyValue>
|
||||
<PropertyValue name="UseAMDCodeGenerationForModulesThatAreNotPartOfAProject">false</PropertyValue>
|
||||
<PropertyValue name="UseCommonJSCodeGenerationForModulesThatAreNotPartOfAProject">false</PropertyValue>
|
||||
<PropertyValue name="UseES2015CodeGenerationForModulesThatAreNotPartOfAProject">false</PropertyValue>
|
||||
<PropertyValue name="UseJsxPreserveForFilesThatAreNotPartOfAProject">false</PropertyValue>
|
||||
<PropertyValue name="UseJsxReactForFilesThatAreNotPartOfAProject">false</PropertyValue>
|
||||
<PropertyValue name="UseSystemCodeGenerationForModulesThatAreNotPartOfAProject">false</PropertyValue>
|
||||
@@ -245,6 +248,7 @@
|
||||
<PropertyValue name="EnableValidation">true</PropertyValue>
|
||||
<PropertyValue name="ErrorsAsWarnings">true</PropertyValue>
|
||||
<PropertyValue name="FormatOnPaste">true</PropertyValue>
|
||||
<PropertyValue name="IdentifyHelpfulExtensions">true</PropertyValue>
|
||||
<PropertyValue name="InsertAttributeValueQuotes">true</PropertyValue>
|
||||
<PropertyValue name="InsertClosingTag">true</PropertyValue>
|
||||
<PropertyValue name="SuggestHelpfulExtensions">false</PropertyValue>
|
||||
|
||||
Reference in New Issue
Block a user