mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
Compare commits
28 Commits
1.10
...
sebros/fil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7d26a3215 | ||
|
|
712ff2f487 | ||
|
|
aae9f6270e | ||
|
|
26e2fe9977 | ||
|
|
b1c606becc | ||
|
|
8b8cf0b7e6 | ||
|
|
7effab96a0 | ||
|
|
1b8bcb8a4e | ||
|
|
026acc4aa9 | ||
|
|
ad6a2b96e5 | ||
|
|
72c9f0063b | ||
|
|
a8d4b18f72 | ||
|
|
1907e57902 | ||
|
|
a806ac7487 | ||
|
|
47072f91ae | ||
|
|
bbe4110053 | ||
|
|
5dfda7c0a8 | ||
|
|
89a5953ea3 | ||
|
|
c61666bfdc | ||
|
|
2749f2fa21 | ||
|
|
fe5c8ba233 | ||
|
|
290f8e4594 | ||
|
|
5811598dff | ||
|
|
2a01771a37 | ||
|
|
a1531da11b | ||
|
|
49022cd1b5 | ||
|
|
4cc48a380d | ||
|
|
f3971484b8 |
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.Environment.Extensions;
|
||||
@@ -18,7 +19,8 @@ namespace Orchard.OutputCache.Services {
|
||||
private readonly IClock _clock;
|
||||
private readonly ISignals _signals;
|
||||
|
||||
private string _root;
|
||||
private string _content;
|
||||
private string _metadata;
|
||||
|
||||
public FileSystemOutputCacheBackgroundTask(
|
||||
IAppDataFolder appDataFolder,
|
||||
@@ -32,22 +34,26 @@ namespace Orchard.OutputCache.Services {
|
||||
_clock = clock;
|
||||
_signals = signals;
|
||||
|
||||
_root = _appDataFolder.Combine("OutputCache", _shellSettings.Name);
|
||||
_metadata = FileSystemOutputCacheStorageProvider.GetMetadataPath(appDataFolder, _shellSettings.Name);
|
||||
_content = FileSystemOutputCacheStorageProvider.GetContentPath(appDataFolder, _shellSettings.Name);
|
||||
}
|
||||
|
||||
public void Sweep() {
|
||||
foreach(var filename in _appDataFolder.ListFiles(_root).ToArray()) {
|
||||
var validUntilUtc = _cacheManager.Get(filename, context => {
|
||||
_signals.When(filename);
|
||||
foreach(var filename in _appDataFolder.ListFiles(_metadata).ToArray()) {
|
||||
var hash = Path.GetFileName(filename);
|
||||
|
||||
var validUntilUtc = _cacheManager.Get(hash, context => {
|
||||
_signals.When(hash);
|
||||
|
||||
using (var stream = _appDataFolder.OpenFile(filename)) {
|
||||
var cacheItem = FileSystemOutputCacheStorageProvider.Deserialize(stream);
|
||||
var cacheItem = FileSystemOutputCacheStorageProvider.DeserializeMetadata(stream);
|
||||
return cacheItem.ValidUntilUtc;
|
||||
}
|
||||
});
|
||||
|
||||
if (_clock.UtcNow > validUntilUtc) {
|
||||
_appDataFolder.DeleteFile(filename);
|
||||
_appDataFolder.DeleteFile(_appDataFolder.Combine(_metadata, hash));
|
||||
_appDataFolder.DeleteFile(_appDataFolder.Combine(_content, hash));
|
||||
_signals.Trigger(filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Orchard.OutputCache.Models;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Services;
|
||||
using Orchard.FileSystems.AppData;
|
||||
using Orchard.Environment.Configuration;
|
||||
using System.Web;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.FileSystems.AppData;
|
||||
using Orchard.Logging;
|
||||
using Orchard.OutputCache.Models;
|
||||
using Orchard.Services;
|
||||
|
||||
namespace Orchard.OutputCache.Services {
|
||||
[OrchardFeature("Orchard.OutputCache.FileSystem")]
|
||||
[OrchardSuppressDependency("Orchard.OutputCache.Services.DefaultCacheStorageProvider")]
|
||||
/// <summary>
|
||||
/// This class provides an implementation of <see cref="IOutputCacheStorageProvider"/>
|
||||
/// based on the local App_Data folder, inside <c>OuputCache/{tenant}</c>. It is not
|
||||
/// recommended when used in a server farm.
|
||||
/// based on the local App_Data folder, inside <c>FileCache/{tenant}</c>. It is not
|
||||
/// recommended when used in a server farm, unless the file system is share (Azure App Services).
|
||||
/// The <see cref="CacheItem"/> instances are binary serialized.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This provider doesn't implement quotas support yet.
|
||||
/// This provider doesn't support quotas yet.
|
||||
/// </remarks>
|
||||
public class FileSystemOutputCacheStorageProvider : IOutputCacheStorageProvider {
|
||||
private readonly IClock _clock;
|
||||
private readonly IAppDataFolder _appDataFolder;
|
||||
private readonly ShellSettings _shellSettings;
|
||||
private readonly string _root;
|
||||
private readonly string _metadata;
|
||||
private readonly string _content;
|
||||
|
||||
public static char[] InvalidPathChars = { '/', '\\', ':', '*', '?', '>', '<', '|' };
|
||||
|
||||
public FileSystemOutputCacheStorageProvider(IClock clock, IAppDataFolder appDataFolder, ShellSettings shellSettings) {
|
||||
_appDataFolder = appDataFolder;
|
||||
_clock = clock;
|
||||
_shellSettings = shellSettings;
|
||||
_root = _appDataFolder.Combine("OutputCache", _shellSettings.Name);
|
||||
|
||||
_metadata = GetMetadataPath(appDataFolder, _shellSettings.Name);
|
||||
_content = GetContentPath(appDataFolder, _shellSettings.Name);
|
||||
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
|
||||
public void Set(string key, CacheItem cacheItem) {
|
||||
Retry(() => {
|
||||
if (cacheItem == null) {
|
||||
@@ -50,82 +56,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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) });
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user