Compare commits

...

28 Commits

Author SHA1 Message Date
Sebastien Ros
d7d26a3215 Fixing the FileSystemOutputCache feature
- Caching keys for filenames to prevent too long paths
- Separating metadata from content storage to optimize some scenarios
- Fixing retry logic
2016-03-14 17:50:21 -07:00
Sebastien Ros
712ff2f487 Retrying on tenant startup
Conflicts:
	src/Orchard.Web/Config/HostComponents.config
	src/Orchard/Environment/DefaultOrchardHost.cs
2016-03-09 15:39:57 -08:00
Sebastien Ros
aae9f6270e [Fixes #6526] Unwrap TargetInvocationException when executing a ShapeAttributeBinding 2016-03-09 15:37:44 -08:00
Sébastien Ros
26e2fe9977 Merge pull request #6419 from rtpHarry/fix-6272
[Fixes #6272] Improve wording for comparisons
2016-03-03 12:41:25 -08:00
Sébastien Ros
b1c606becc Merge pull request #6426 from rtpHarry/terms-mentions-taxonomies
Fix wrong permission check and incorrect use of taxonomies phrase
2016-03-03 12:37:27 -08:00
Sébastien Ros
8b8cf0b7e6 Merge pull request #6427 from rtpHarry/module-list-flexbox
[Fixes #5907] Convert modules admin page to use css flexbox
2016-03-03 12:36:36 -08:00
Sébastien Ros
7effab96a0 Merge pull request #6429 from rtpHarry/fix-5706
[Fixes #5706] Force order on Properties, Validation, Bindings
2016-03-03 12:33:38 -08:00
Sébastien Ros
1b8bcb8a4e Merge pull request #6500 from infofromca/tenantPrefixHost
[Fixes #6495] Ensure a host or a url is defined for new tenants
2016-03-03 12:26:53 -08:00
andy zheng
026acc4aa9 fixed one of URL prefix and Host should be required 2016-02-28 13:30:11 -05:00
Sébastien Ros
ad6a2b96e5 Merge pull request #6428 from rtpHarry/fix-packaging-display-glitch
Fixing display glitch in admin packaging/gallery/modules
2016-02-25 13:06:44 -08:00
Sébastien Ros
72c9f0063b Merge pull request #6433 from rtpHarry/fix-5708
[Fixes #5708] Display mandatory checkbox as required
2016-02-19 15:50:52 -08:00
Matthew Harris
a8d4b18f72 fixes #5708 - change ui layer to display Mandatory checkbox as required 2016-02-19 22:44:52 +00:00
Matthew Harris
1907e57902 fixes #5706 - standardize order on Properties, Validation, Bindings 2016-02-19 02:27:23 +00:00
Matthew Harris
a806ac7487 fix display glitch in admin packaging/gallery/modules 2016-02-19 01:56:33 +00:00
Sébastien Ros
47072f91ae Merge pull request #6425 from rtpHarry/widget-ruleengine-typo
Fix typo in Orchard.Widgets.RuleEngine.cs
2016-02-18 17:50:45 -08:00
Matthew Harris
bbe4110053 fix typo in typo fix 2016-02-19 01:43:55 +00:00
Matthew Harris
5dfda7c0a8 convert modules admin page to use css flexbox 2016-02-19 01:36:28 +00:00
Matthew Harris
89a5953ea3 fix wrong permission check and incorrect use of taxonomies phrase 2016-02-19 00:45:54 +00:00
Matthew Harris
c61666bfdc fix typo in Orchard.Widgets.RuleEngine.cs 2016-02-19 00:32:40 +00:00
Sébastien Ros
2749f2fa21 Merge pull request #6424 from rtpHarry/fix-taxonomy-permission
fixes #6423 - wrong permission checked for delete
2016-02-18 16:30:53 -08:00
Matthew Harris
fe5c8ba233 fixes #6423 - wrong permission checked for delete 2016-02-19 00:27:53 +00:00
Sébastien Ros
290f8e4594 Merge pull request #6420 from rtpHarry/fix-6400
fixes #6400 - improve notifier messages for terms admin
2016-02-18 16:05:49 -08:00
Matthew Harris
5811598dff fixes 6400 - improve notifier messages for terms admin 2016-02-18 22:01:52 +00:00
Matthew Harris
2a01771a37 fixes #6272 update wording for comparisons 2016-02-18 21:44:57 +00:00
Sébastien Ros
a1531da11b Merge pull request #6301 from Codinlab/fixes/5593
Fixes #5593 : Allow disabling HtmlEncode on DynamicForms
2016-02-18 12:55:31 -08:00
Lombiq
49022cd1b5 To the previous: also caring about the case when ShapePart was dynamically added to the content type/item, see #6360 2016-02-18 16:32:16 +01:00
Lombiq
4cc48a380d You can't create two Template content items with the same name (title) any more, fixes #6360 2016-02-17 21:05:14 +01:00
Thierry Fleury
f3971484b8 Fixes #5593 : Dynamic Forms: In form settings HtmlEncode option is always checked 2016-01-23 10:32:22 +01:00
20 changed files with 302 additions and 93 deletions

View File

@@ -106,5 +106,14 @@
</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>

View File

@@ -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);
}

View File

@@ -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",

View File

@@ -83,7 +83,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",

View File

@@ -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); }
}

View File

@@ -18,7 +18,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);
}
}

View File

@@ -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%;
}

View File

@@ -68,6 +68,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);
}
@@ -139,6 +143,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);
}

View File

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

View File

@@ -1,45 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.OutputCache.Models;
using Orchard.Environment.Extensions;
using Orchard.Logging;
using Orchard.Services;
using Orchard.FileSystems.AppData;
using Orchard.Environment.Configuration;
using System.Web;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Text;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions;
using Orchard.FileSystems.AppData;
using Orchard.Logging;
using Orchard.OutputCache.Models;
using Orchard.Services;
namespace Orchard.OutputCache.Services {
[OrchardFeature("Orchard.OutputCache.FileSystem")]
[OrchardSuppressDependency("Orchard.OutputCache.Services.DefaultCacheStorageProvider")]
/// <summary>
/// This class provides an implementation of <see cref="IOutputCacheStorageProvider"/>
/// based on the local App_Data folder, inside <c>OuputCache/{tenant}</c>. It is not
/// recommended when used in a server farm.
/// based on the local App_Data folder, inside <c>FileCache/{tenant}</c>. It is not
/// recommended when used in a server farm, unless the file system is share (Azure App Services).
/// The <see cref="CacheItem"/> instances are binary serialized.
/// </summary>
/// <remarks>
/// This provider doesn't implement quotas support yet.
/// This provider doesn't support quotas yet.
/// </remarks>
public class FileSystemOutputCacheStorageProvider : IOutputCacheStorageProvider {
private readonly IClock _clock;
private readonly IAppDataFolder _appDataFolder;
private readonly ShellSettings _shellSettings;
private readonly string _root;
private readonly string _metadata;
private readonly string _content;
public static char[] InvalidPathChars = { '/', '\\', ':', '*', '?', '>', '<', '|' };
public FileSystemOutputCacheStorageProvider(IClock clock, IAppDataFolder appDataFolder, ShellSettings shellSettings) {
_appDataFolder = appDataFolder;
_clock = clock;
_shellSettings = shellSettings;
_root = _appDataFolder.Combine("OutputCache", _shellSettings.Name);
_metadata = GetMetadataPath(appDataFolder, _shellSettings.Name);
_content = GetContentPath(appDataFolder, _shellSettings.Name);
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public void Set(string key, CacheItem cacheItem) {
Retry(() => {
if (cacheItem == null) {
@@ -50,82 +56,170 @@ namespace Orchard.OutputCache.Services {
return;
}
var filename = GetCacheItemFilename(key);
var hash = GetCacheItemFileHash(key);
using (var stream = Serialize(cacheItem)) {
using (var fileStream = _appDataFolder.CreateFile(filename)) {
stream.CopyTo(fileStream);
lock (String.Intern(hash)) {
using (var stream = SerializeContent(cacheItem)) {
var filename = _appDataFolder.Combine(_content, hash);
using (var fileStream = _appDataFolder.CreateFile(filename)) {
stream.CopyTo(fileStream);
}
}
using (var stream = SerializeMetadata(cacheItem)) {
var 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;
}
var content = DeserializeContent(stream);
cacheItem.Output = content;
}
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) {
public static string GetMetadataPath(IAppDataFolder appDataFolder, string tenant) {
return appDataFolder.Combine("FileCache", tenant, "metadata");
}
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 SerializeContent(CacheItem item) {
BinaryFormatter binaryFormatter = new BinaryFormatter();
var memoryStream = new MemoryStream();
binaryFormatter.Serialize(memoryStream, item);
binaryFormatter.Serialize(memoryStream, item.Output);
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
internal static CacheItem Deserialize(Stream stream) {
internal static byte[] DeserializeContent(Stream stream) {
BinaryFormatter binaryFormatter = new BinaryFormatter();
var result = (byte[])binaryFormatter.Deserialize(stream);
return result;
}
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 +232,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 +249,12 @@ namespace Orchard.OutputCache.Services {
for(int i=1; i <= retries; i++) {
try {
action();
return;
}
catch {
if(i == retries) {
catch(Exception e) {
Logger.Warning("An unexpected error occured, attempt # {0}, i", e);
if (i == retries) {
throw;
}
}

View File

@@ -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 {

View File

@@ -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:

View File

@@ -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:

View File

@@ -68,7 +68,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);

View File

@@ -10,6 +10,7 @@ using Orchard.UI.Admin;
using Orchard.ContentManagement;
using Orchard.UI.Notify;
using Orchard.Taxonomies.Helpers;
using Orchard.Mvc.Html;
namespace Orchard.Taxonomies.Controllers {
[ValidateInput(false), Admin]
@@ -44,15 +45,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")))
@@ -74,8 +84,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 });
}
@@ -181,7 +189,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);
@@ -194,7 +202,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);

View File

@@ -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;
}
}
}

View File

@@ -162,6 +162,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" />

View File

@@ -21,7 +21,7 @@ namespace Orchard.Widgets.RuleEngine {
public bool Matches(string expression) {
var evaluator = _evaluators.FirstOrDefault();
if (evaluator == null) {
throw new OrchardException(T("There are currently not scripting engine enabled"));
throw new OrchardException(T("There are currently no scripting engines enabled"));
}
var result = evaluator.Evaluate(expression, new List<IGlobalMethodProvider> { new GlobalMethodProvider(this) });

View File

@@ -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) {

View File

@@ -14,6 +14,7 @@ using Orchard.Localization;
using Orchard.Logging;
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
@@ -32,6 +33,10 @@ namespace Orchard.Environment {
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,
@@ -137,16 +142,32 @@ 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) {
// Waits for i^2 which means 1, 2, 4, 8 ... seconds
Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(i, 2)));
}
bool failed = false;
try {
var context = CreateShellContext(settings);
ActivateShell(context);
}
catch (Exception ex) {
// An exception at this point is always fatal as it literally kills the
// tenant. What is more fatal than something that kills you?
Logger.Error(ex, "A tenant could not be started: " + settings.Name + " Attempt number: " + i);
failed = true;
}
if(failed && i == Retries) {
Logger.Fatal("A tenant could not be started: {0} after {1} retries.", settings.Name, Retries);
return;
}
}
while (_processingEngine.AreTasksPending()) {
Logger.Debug("Processing pending task after activate Shell");
_processingEngine.ExecuteNextTask();