diff --git a/src/Orchard.Specs/MultiTenancy.feature b/src/Orchard.Specs/MultiTenancy.feature index 7134c19c9..4701fd9ef 100644 --- a/src/Orchard.Specs/MultiTenancy.feature +++ b/src/Orchard.Specs/MultiTenancy.feature @@ -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 "

Scott\s*

" @@ -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 "
  • " @@ -199,4 +201,4 @@ Scenario: Listing tenants from command line And I have tenant "Alpha" on "example.org" as "New-site-name" When I execute >tenant list Then I should see "Name: Alpha" - And I should see "Request URL host: example.org" \ No newline at end of file + And I should see "Request URL host: example.org" diff --git a/src/Orchard.Specs/MultiTenancy.feature.cs b/src/Orchard.Specs/MultiTenancy.feature.cs index f2867c62f..5cee8d1d6 100644 --- a/src/Orchard.Specs/MultiTenancy.feature.cs +++ b/src/Orchard.Specs/MultiTenancy.feature.cs @@ -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 diff --git a/src/Orchard.Web/Config/HostComponents.config b/src/Orchard.Web/Config/HostComponents.config index 0e3fddd25..da8b3838a 100644 --- a/src/Orchard.Web/Config/HostComponents.config +++ b/src/Orchard.Web/Config/HostComponents.config @@ -120,5 +120,13 @@ + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.DynamicForms/Drivers/BindingsElementDriver.cs b/src/Orchard.Web/Modules/Orchard.DynamicForms/Drivers/BindingsElementDriver.cs index f30894420..fa7c2ee13 100644 --- a/src/Orchard.Web/Modules/Orchard.DynamicForms/Drivers/BindingsElementDriver.cs +++ b/src/Orchard.Web/Modules/Orchard.DynamicForms/Drivers/BindingsElementDriver.cs @@ -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); } diff --git a/src/Orchard.Web/Modules/Orchard.DynamicForms/Drivers/CheckboxElementDriver.cs b/src/Orchard.Web/Modules/Orchard.DynamicForms/Drivers/CheckboxElementDriver.cs index 25392e55c..beb915651 100644 --- a/src/Orchard.Web/Modules/Orchard.DynamicForms/Drivers/CheckboxElementDriver.cs +++ b/src/Orchard.Web/Modules/Orchard.DynamicForms/Drivers/CheckboxElementDriver.cs @@ -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", diff --git a/src/Orchard.Web/Modules/Orchard.DynamicForms/Drivers/FormElementDriver.cs b/src/Orchard.Web/Modules/Orchard.DynamicForms/Drivers/FormElementDriver.cs index 16ad4c8fa..f054e4d4d 100644 --- a/src/Orchard.Web/Modules/Orchard.DynamicForms/Drivers/FormElementDriver.cs +++ b/src/Orchard.Web/Modules/Orchard.DynamicForms/Drivers/FormElementDriver.cs @@ -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", diff --git a/src/Orchard.Web/Modules/Orchard.DynamicForms/Elements/Form.cs b/src/Orchard.Web/Modules/Orchard.DynamicForms/Elements/Form.cs index 2c6c44996..cd9c211db 100644 --- a/src/Orchard.Web/Modules/Orchard.DynamicForms/Elements/Form.cs +++ b/src/Orchard.Web/Modules/Orchard.DynamicForms/Elements/Form.cs @@ -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); } } diff --git a/src/Orchard.Web/Modules/Orchard.DynamicForms/ValidationRules/Mandatory.cs b/src/Orchard.Web/Modules/Orchard.DynamicForms/ValidationRules/Mandatory.cs index b5092943c..9193fc953 100644 --- a/src/Orchard.Web/Modules/Orchard.DynamicForms/ValidationRules/Mandatory.cs +++ b/src/Orchard.Web/Modules/Orchard.DynamicForms/ValidationRules/Mandatory.cs @@ -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); } } diff --git a/src/Orchard.Web/Modules/Orchard.Lists/Shapes.cs b/src/Orchard.Web/Modules/Orchard.Lists/Shapes.cs index 0f0edeece..405dc40a3 100644 --- a/src/Orchard.Web/Modules/Orchard.Lists/Shapes.cs +++ b/src/Orchard.Web/Modules/Orchard.Lists/Shapes.cs @@ -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); @@ -32,4 +34,4 @@ namespace Orchard.Lists { }); } } -} \ No newline at end of file +} diff --git a/src/Orchard.Web/Modules/Orchard.Lists/Views/Admin/List.cshtml b/src/Orchard.Web/Modules/Orchard.Lists/Views/Admin/List.cshtml index af414e8fd..7b32b3643 100644 --- a/src/Orchard.Web/Modules/Orchard.Lists/Views/Admin/List.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Lists/Views/Admin/List.cshtml @@ -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(); @@ -39,4 +39,4 @@ @if (Model.ListNavigation != null) { @Display(Model.ListNavigation) -} \ No newline at end of file +} diff --git a/src/Orchard.Web/Modules/Orchard.Modules/styles/orchard-modules-admin.css b/src/Orchard.Web/Modules/Orchard.Modules/styles/orchard-modules-admin.css index 88b146380..09b1e59c3 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/styles/orchard-modules-admin.css +++ b/src/Orchard.Web/Modules/Orchard.Modules/styles/orchard-modules-admin.css @@ -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%; } diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Controllers/AdminController.cs index 6cf12eb82..a3fef98d3 100644 --- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Controllers/AdminController.cs @@ -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); } diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs index 860236eef..00013de70 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs @@ -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().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(); - 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; @@ -476,12 +486,27 @@ namespace Orchard.OutputCache.Filters { response.AddHeader("X-Cached-On", cacheItem.CachedOnUtc.ToString("r")); response.AddHeader("X-Cached-Until", cacheItem.ValidUntilUtc.ToString("r")); } - + // 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.DisableKernelCache(); // response.Cache.SetOmitVaryStar(true); - // 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; } @@ -628,4 +644,4 @@ namespace Orchard.OutputCache.Filters { public class ViewDataContainer : IViewDataContainer { public ViewDataDictionary ViewData { get; set; } } -} \ No newline at end of file +} diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs index 29243df6f..aeeff8af0 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs @@ -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; } diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Services/DefaultCacheStorageProvider.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Services/DefaultCacheStorageProvider.cs index 13d684659..9f219fd74 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Services/DefaultCacheStorageProvider.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Services/DefaultCacheStorageProvider.cs @@ -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, diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Styles/orchard-packaging-admin.css b/src/Orchard.Web/Modules/Orchard.Packaging/Styles/orchard-packaging-admin.css index 8e7e369b7..e8c93ea06 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Styles/orchard-packaging-admin.css +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Styles/orchard-packaging-admin.css @@ -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 { diff --git a/src/Orchard.Web/Modules/Orchard.Projections/FilterEditors/Forms/DateTimeFilterForm.cs b/src/Orchard.Web/Modules/Orchard.Projections/FilterEditors/Forms/DateTimeFilterForm.cs index 28dbe68e9..cbca13934 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/FilterEditors/Forms/DateTimeFilterForm.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/FilterEditors/Forms/DateTimeFilterForm.cs @@ -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: diff --git a/src/Orchard.Web/Modules/Orchard.Projections/FilterEditors/Forms/NumericFilterForm.cs b/src/Orchard.Web/Modules/Orchard.Projections/FilterEditors/Forms/NumericFilterForm.cs index eb37f44d9..f0ead2a0d 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/FilterEditors/Forms/NumericFilterForm.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/FilterEditors/Forms/NumericFilterForm.cs @@ -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: diff --git a/src/Orchard.Web/Modules/Orchard.Taxonomies/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Taxonomies/Controllers/AdminController.cs index 962ffa310..394c58ebf 100644 --- a/src/Orchard.Web/Modules/Orchard.Taxonomies/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.Taxonomies/Controllers/AdminController.cs @@ -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); diff --git a/src/Orchard.Web/Modules/Orchard.Taxonomies/Controllers/TermAdminController.cs b/src/Orchard.Web/Modules/Orchard.Taxonomies/Controllers/TermAdminController.cs index 2b9c1cc94..d85f224ef 100644 --- a/src/Orchard.Web/Modules/Orchard.Taxonomies/Controllers/TermAdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.Taxonomies/Controllers/TermAdminController.cs @@ -70,15 +70,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 +109,6 @@ namespace Orchard.Taxonomies.Controllers { throw new ArgumentOutOfRangeException(); } - Services.Notifier.Information(T("{0} term have been removed.", checkedEntries.Count)); - return RedirectToAction("Index", new { taxonomyId = viewModel.TaxonomyId }); } @@ -207,7 +214,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 +227,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); diff --git a/src/Orchard.Web/Modules/Orchard.Templates/Drivers/TitlePartDriver.cs b/src/Orchard.Web/Modules/Orchard.Templates/Drivers/TitlePartDriver.cs new file mode 100644 index 000000000..2a7b6c37c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Templates/Drivers/TitlePartDriver.cs @@ -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 { + 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()) { + 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(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; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Templates/Orchard.Templates.csproj b/src/Orchard.Web/Modules/Orchard.Templates/Orchard.Templates.csproj index 6aed37fb9..d8ebcb7be 100644 --- a/src/Orchard.Web/Modules/Orchard.Templates/Orchard.Templates.csproj +++ b/src/Orchard.Web/Modules/Orchard.Templates/Orchard.Templates.csproj @@ -180,6 +180,7 @@ + diff --git a/src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs b/src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs index 22ad79ca7..3dbe111e8 100644 --- a/src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs +++ b/src/Orchard/DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs @@ -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) { diff --git a/src/Orchard/Environment/DefaultOrchardHost.cs b/src/Orchard/Environment/DefaultOrchardHost.cs index ec86af817..c14be633a 100644 --- a/src/Orchard/Environment/DefaultOrchardHost.cs +++ b/src/Orchard/Environment/DefaultOrchardHost.cs @@ -15,6 +15,7 @@ using Orchard.Mvc; using Orchard.Mvc.Extensions; using Orchard.Utility.Extensions; using Orchard.Exceptions; +using System.Threading; namespace Orchard.Environment { // All the event handlers that DefaultOrchardHost implements have to be declared in OrchardStarter. @@ -34,6 +35,9 @@ namespace Orchard.Environment { private IEnumerable _shellContexts; private readonly ContextState> _tenantsToRestart; + + public bool DelayRetries { get; set; } + public DefaultOrchardHost( IShellSettingsManager shellSettingsManager, IShellContextFactory shellContextFactory, @@ -142,16 +146,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; - } - Logger.Error(ex, "A tenant could not be started: " + settings.Name); + 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))); + } + + 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(); diff --git a/src/Orchard/Environment/DefaultOrchardShell.cs b/src/Orchard/Environment/DefaultOrchardShell.cs index dcca7e9a6..618c8d9a9 100644 --- a/src/Orchard/Environment/DefaultOrchardShell.cs +++ b/src/Orchard/Environment/DefaultOrchardShell.cs @@ -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"); } } } diff --git a/src/Rebracer.xml b/src/Rebracer.xml index df145c351..9d83e6ac7 100644 --- a/src/Rebracer.xml +++ b/src/Rebracer.xml @@ -130,6 +130,7 @@ false true true + false false false false @@ -154,6 +155,7 @@ 2 2 false + false 0 true true @@ -235,6 +237,7 @@ false false false + false false false false @@ -245,6 +248,7 @@ true true true + true true true false