diff --git a/.hgsubstate b/.hgsubstate index cf08300bd..68073552e 100644 --- a/.hgsubstate +++ b/.hgsubstate @@ -1,5 +1,5 @@ eb12bf266fcd8334b7a20d7553dd28f9c80dfa07 src/Orchard.Web/Modules/Orchard.Forms -8a370b966ac7ccbb6564af4b748084c2e68438d3 src/Orchard.Web/Modules/Orchard.Projections +6668e827c75141ac2f49664a1f6461b8aa7ee618 src/Orchard.Web/Modules/Orchard.Projections 7e6e3d2e72d94c3f78e8aecc0aa87ede108226bd src/Orchard.Web/Modules/Orchard.Rules c6098ad6c3fdb614901e8946d8e298e3c50f3ea1 src/Orchard.Web/Modules/Orchard.TaskLease 4c4fd36df9d1337c0b6801960d02221debcf4388 src/Orchard.Web/Modules/Orchard.Tokens diff --git a/src/Orchard.Specs/Orchard.Specs.csproj b/src/Orchard.Specs/Orchard.Specs.csproj index 3443dfe74..d12bf5b82 100644 --- a/src/Orchard.Specs/Orchard.Specs.csproj +++ b/src/Orchard.Specs/Orchard.Specs.csproj @@ -63,10 +63,6 @@ False ..\..\lib\autofac\Autofac.Integration.Web.dll - - False - ..\..\lib\autofac\Autofac.Integration.Web.Mvc.dll - False ..\..\lib\Castle Windsor 2.0\bin\Castle.Core.dll diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index 46a7febf0..0ba5e7872 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -63,10 +63,6 @@ False ..\..\lib\autofac\Autofac.Integration.Web.dll - - False - ..\..\lib\autofac\Autofac.Integration.Web.Mvc.dll - False ..\..\lib\Castle Windsor 2.0\bin\Castle.Core.dll @@ -278,6 +274,7 @@ + diff --git a/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs b/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs index 807ffc968..89af557e5 100644 --- a/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs +++ b/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs @@ -77,6 +77,8 @@ namespace Orchard.Tests.Stubs { } public string BaseUrl { get; set;} + + public string SiteTimeZone { get; set; } } public class StubUser : IUser { diff --git a/src/Orchard.Tests/Time/TimeZoneSelectorTests.cs b/src/Orchard.Tests/Time/TimeZoneSelectorTests.cs new file mode 100644 index 000000000..b0cee916b --- /dev/null +++ b/src/Orchard.Tests/Time/TimeZoneSelectorTests.cs @@ -0,0 +1,85 @@ +using System; +using System.Web; +using Autofac; +using NUnit.Framework; +using Orchard.Tests.Stubs; +using Orchard.Time; + +namespace Orchard.Tests.Time { + [TestFixture] + public class TimeZoneProviderTests { + private IContainer _container; + private IWorkContextStateProvider _workContextStateProvider; + private TestTimeZoneSelector _timeZoneSelector; + + [SetUp] + public void Init() { + var builder = new ContainerBuilder(); + builder.RegisterInstance(_timeZoneSelector = new TestTimeZoneSelector()).As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + + _container = builder.Build(); + _workContextStateProvider = _container.Resolve(); + } + + [Test] + public void ShouldProvideCurrentTimeZoneOnly() { + _timeZoneSelector.TimeZone = null; + var timeZone = _workContextStateProvider.Get("Foo"); + + Assert.That(timeZone, Is.Null); + } + + [Test] + public void DefaultTimeZoneIsLocal() { + _timeZoneSelector.Priority = -200; + var timeZone = _workContextStateProvider.Get("CurrentTimeZone"); + + Assert.That(timeZone(new StubWorkContext()), Is.EqualTo(TimeZoneInfo.Local)); + } + + [Test] + public void TimeZoneProviderReturnsTimeZoneFromSelector() { + _timeZoneSelector.Priority = 999; + _timeZoneSelector.TimeZone = TimeZoneInfo.Utc; + var timeZone = _workContextStateProvider.Get("CurrentTimeZone"); + + Assert.That(timeZone(new StubWorkContext()), Is.EqualTo(TimeZoneInfo.Utc)); + } + + } + + public class TestTimeZoneSelector : ITimeZoneSelector { + public TimeZoneInfo TimeZone { get; set; } + public int Priority { get; set; } + + public TimeZoneSelectorResult GetTimeZone(HttpContextBase context) { + return new TimeZoneSelectorResult { + Priority = Priority, + TimeZone= TimeZone + }; + } + } + + public class StubWorkContext : WorkContext { + public override T Resolve() { + throw new NotImplementedException(); + } + + public override bool TryResolve(out T service) { + throw new NotImplementedException(); + } + + public override T GetState(string name) { + return default(T); + } + + public override void SetState(string name, T value) { + + } + } +} + diff --git a/src/Orchard.Web/Core/Common/Shapes.cs b/src/Orchard.Web/Core/Common/Shapes.cs index 075ea07eb..3c0287efe 100644 --- a/src/Orchard.Web/Core/Common/Shapes.cs +++ b/src/Orchard.Web/Core/Common/Shapes.cs @@ -23,12 +23,12 @@ namespace Orchard.Core.Common { } [Shape] - public IHtmlString PublishedState(HtmlHelper html, DateTime createdDateTimeUtc, DateTime? publisheddateTimeUtc) { + public IHtmlString PublishedState(dynamic Display, DateTime createdDateTimeUtc, DateTime? publisheddateTimeUtc) { if (!publisheddateTimeUtc.HasValue) { return T("Draft"); } - return html.DateTime(createdDateTimeUtc); + return Display.DateTime(DateTimeUtc: createdDateTimeUtc); } [Shape] diff --git a/src/Orchard.Web/Core/Settings/Drivers/SiteSettingsPartDriver.cs b/src/Orchard.Web/Core/Settings/Drivers/SiteSettingsPartDriver.cs index b750f9bf5..81cd50525 100644 --- a/src/Orchard.Web/Core/Settings/Drivers/SiteSettingsPartDriver.cs +++ b/src/Orchard.Web/Core/Settings/Drivers/SiteSettingsPartDriver.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.Linq; +using System.Net; using JetBrains.Annotations; using Orchard.ContentManagement; using Orchard.ContentManagement.Drivers; @@ -44,7 +45,8 @@ namespace Orchard.Core.Settings.Drivers { var model = new SiteSettingsPartViewModel { Site = site, - SiteCultures = _cultureManager.ListCultures() + SiteCultures = _cultureManager.ListCultures(), + TimeZones = TimeZoneInfo.GetSystemTimeZones() }; return ContentShape("Parts_Settings_SiteSettingsPart", @@ -55,7 +57,8 @@ namespace Orchard.Core.Settings.Drivers { var site = _siteService.GetSiteSettings().As(); var model = new SiteSettingsPartViewModel { Site = site, - SiteCultures = _cultureManager.ListCultures() + SiteCultures = _cultureManager.ListCultures(), + TimeZones = TimeZoneInfo.GetSystemTimeZones() }; var previousBaseUrl = model.Site.BaseUrl; diff --git a/src/Orchard.Web/Core/Settings/Migrations.cs b/src/Orchard.Web/Core/Settings/Migrations.cs index bc0ca6290..56b20fb39 100644 --- a/src/Orchard.Web/Core/Settings/Migrations.cs +++ b/src/Orchard.Web/Core/Settings/Migrations.cs @@ -91,9 +91,16 @@ namespace Orchard.Core.Settings { .Column("SiteCulture") .Column("ResourceDebugMode", c => c.WithDefault("FromAppSetting")) .Column("PageSize") + .Column("SiteTimeZone") ); - return 1; + SchemaBuilder.CreateTable("SiteSettings2PartRecord", + table => table + .ContentPartRecord() + .Column("BaseUrl", c => c.Unlimited()) + ); + + return 3; } public int UpdateFrom1() { @@ -105,5 +112,14 @@ namespace Orchard.Core.Settings { return 2; } + + public int UpdateFrom2() { + SchemaBuilder.AlterTable("SiteSettingsPartRecord", + table => table + .AddColumn("SiteTimeZone") + ); + + return 3; + } } } \ No newline at end of file diff --git a/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs b/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs index bf83d1f55..071f81bce 100644 --- a/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs +++ b/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using Orchard.ContentManagement; using Orchard.Data.Conventions; using Orchard.Settings; @@ -45,6 +46,11 @@ namespace Orchard.Core.Settings.Models { set { Record.PageSize = value; } } + public string SiteTimeZone { + get { return Record.SiteTimeZone; } + set { Record.SiteTimeZone = value; } + } + [StringLengthMax] public string BaseUrl { get { diff --git a/src/Orchard.Web/Core/Settings/Models/SiteSettingsPartRecord.cs b/src/Orchard.Web/Core/Settings/Models/SiteSettingsPartRecord.cs index eb87877f5..6d85341be 100644 --- a/src/Orchard.Web/Core/Settings/Models/SiteSettingsPartRecord.cs +++ b/src/Orchard.Web/Core/Settings/Models/SiteSettingsPartRecord.cs @@ -1,4 +1,5 @@ -using Orchard.ContentManagement.Records; +using System; +using Orchard.ContentManagement.Records; using Orchard.Settings; namespace Orchard.Core.Settings.Models { @@ -24,5 +25,7 @@ namespace Orchard.Core.Settings.Models { public virtual ResourceDebugMode ResourceDebugMode { get; set; } public virtual int PageSize { get; set; } + + public virtual string SiteTimeZone { get; set; } } } \ No newline at end of file diff --git a/src/Orchard.Web/Core/Settings/Services/SiteService.cs b/src/Orchard.Web/Core/Settings/Services/SiteService.cs index 5fc76e488..603994413 100644 --- a/src/Orchard.Web/Core/Settings/Services/SiteService.cs +++ b/src/Orchard.Web/Core/Settings/Services/SiteService.cs @@ -36,6 +36,7 @@ namespace Orchard.Core.Settings.Services { item.Record.SiteSalt = Guid.NewGuid().ToString("N"); item.Record.SiteName = "My Orchard Project Application"; item.Record.PageTitleSeparator = " - "; + item.Record.SiteTimeZone = TimeZoneInfo.Local.Id; }).ContentItem; } diff --git a/src/Orchard.Web/Core/Settings/ViewModels/SiteSettingsPartViewModel.cs b/src/Orchard.Web/Core/Settings/ViewModels/SiteSettingsPartViewModel.cs index e140e8841..011e0f1ca 100644 --- a/src/Orchard.Web/Core/Settings/ViewModels/SiteSettingsPartViewModel.cs +++ b/src/Orchard.Web/Core/Settings/ViewModels/SiteSettingsPartViewModel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Web.Mvc; using Orchard.Core.Settings.Models; using Orchard.Settings; @@ -7,6 +8,7 @@ namespace Orchard.Core.Settings.ViewModels { public class SiteSettingsPartViewModel { public SiteSettingsPart Site { get; set; } public IEnumerable SiteCultures { get; set; } + public IEnumerable TimeZones { get; set; } [HiddenInput(DisplayValue = false)] public int Id { @@ -47,5 +49,10 @@ namespace Orchard.Core.Settings.ViewModels { get { return Site.BaseUrl; } set { Site.BaseUrl = value; } } + + public string TimeZone { + get { return Site.SiteTimeZone; } + set { Site.SiteTimeZone = value; } + } } } diff --git a/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml b/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml index be3daaadb..afc4690b1 100644 --- a/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml +++ b/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml @@ -26,6 +26,12 @@ @Html.ValidationMessage("SiteCulture", "*")

@Html.ActionLink(T("Add or remove supported cultures for the site.").ToString(), "Culture")

+
+ + @Html.DropDownList("TimeZone", new[] { new SelectListItem { Text = T("Local to server").Text, Value = "" } }.Union(new SelectList(Model.TimeZones, "Id", "", Model.TimeZone))) + @Html.ValidationMessage("TimeZone", "*") + @T("Determines the default time zone which will should be used to display date and times.") +
@Html.EditorFor(x => x.PageTitleSeparator) diff --git a/src/Orchard.Web/Core/Shapes/DateTimeShapes.cs b/src/Orchard.Web/Core/Shapes/DateTimeShapes.cs index 7270d01fa..ab9c39fa6 100644 --- a/src/Orchard.Web/Core/Shapes/DateTimeShapes.cs +++ b/src/Orchard.Web/Core/Shapes/DateTimeShapes.cs @@ -7,22 +7,27 @@ using Orchard.Mvc.Html; using Orchard.Services; namespace Orchard.Core.Shapes { - public class DateTimeShapes : ISingletonDependency { + public class DateTimeShapes : IDependency { private readonly IClock _clock; + private readonly IWorkContextAccessor _workContextAccessor; - public DateTimeShapes(IClock clock) { + public DateTimeShapes( + IClock clock, + IWorkContextAccessor workContextAccessor + ) { _clock = clock; + _workContextAccessor = workContextAccessor; T = NullLocalizer.Instance; } public Localizer T { get; set; } [Shape] - public IHtmlString DateTimeRelative(HtmlHelper Html, DateTime dateTimeUtc) { + public IHtmlString DateTimeRelative(dynamic Display, DateTime dateTimeUtc) { var time = _clock.UtcNow - dateTimeUtc; if (time.TotalDays > 7) - return Html.DateTime(dateTimeUtc.ToLocalTime(), T("'on' MMM d yyyy 'at' h:mm tt")); + return Display.DateTime(DateTimeUtc: dateTimeUtc, CustomFormat: T("'on' MMM d yyyy 'at' h:mm tt")); if (time.TotalHours > 24) return T.Plural("1 day ago", "{0} days ago", time.Days); if (time.TotalMinutes > 60) @@ -34,5 +39,30 @@ namespace Orchard.Core.Shapes { return T("a moment ago"); } + + [Shape] + public IHtmlString DateTime(DateTime DateTimeUtc, LocalizedString CustomFormat) { + //using a LocalizedString forces the caller to use a localizable format + + if (CustomFormat == null || String.IsNullOrWhiteSpace(CustomFormat.Text)) { + return DateTime(DateTimeUtc, T("MMM d yyyy h:mm tt")); + } + + return new MvcHtmlString(ConvertToDisplayTime(DateTimeUtc).ToString(CustomFormat.Text)); + } + + /// + /// Converts a Coordinated Universal Time (UTC) to the time in the current time zone. + /// + /// The Coordinated Universal Time (UTC). + /// The date and time in the selected time zone. Its System.DateTime.Kind property is System.DateTimeKind.Utc if the current zone is System.TimeZoneInfo.Utc; otherwise, its System.DateTime.Kind property is System.DateTimeKind.Unspecified. + private DateTime ConvertToDisplayTime(DateTime dateTimeUtc) { + + // get the time zone for the current request + var timeZone = _workContextAccessor.GetContext().CurrentTimeZone; + + return TimeZoneInfo.ConvertTimeFromUtc(dateTimeUtc, timeZone); + } + } } diff --git a/src/Orchard.Web/Modules/Orchard.ArchiveLater/Views/Parts/ArchiveLater.Metadata.SummaryAdmin.cshtml b/src/Orchard.Web/Modules/Orchard.ArchiveLater/Views/Parts/ArchiveLater.Metadata.SummaryAdmin.cshtml index 35d3625f1..a9b35ac90 100644 --- a/src/Orchard.Web/Modules/Orchard.ArchiveLater/Views/Parts/ArchiveLater.Metadata.SummaryAdmin.cshtml +++ b/src/Orchard.Web/Modules/Orchard.ArchiveLater/Views/Parts/ArchiveLater.Metadata.SummaryAdmin.cshtml @@ -4,7 +4,7 @@
  • @T(@T("Unpublish on") - @Html.DateTime((DateTime)Model.ScheduledArchiveUtc.ToLocalTime(), T("M/d/yyyy h:mm tt")) + @Display.DateTime(DateTimeUtc: (DateTime)Model.ScheduledArchiveUtc.ToLocalTime(), CustomFormat: T("M/d/yyyy h:mm tt"))  | 
diff --git a/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Details.cshtml b/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Details.cshtml index 990ad322f..b63376f7f 100644 --- a/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Details.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Details.cshtml @@ -83,7 +83,7 @@ @text } - @Html.DateTime(commentEntry.Comment.CommentDateUtc.GetValueOrDefault()) + @Display.DateTime(DateTimeUtc: commentEntry.Comment.CommentDateUtc.GetValueOrDefault())
  • diff --git a/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Index.cshtml b/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Index.cshtml index 1c198ee97..3e8fba850 100644 --- a/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Index.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Index.cshtml @@ -77,7 +77,7 @@ @* would ideally have permalinks for individual comments *@ -

    +

    @if (commentEntry.Comment.CommentText != null) { var ellipsized = Html.Ellipsize(commentEntry.Comment.CommentText, 500); var paragraphed = new HtmlString(ellipsized.ToHtmlString().Replace("\r\n", "

    ")); diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/EditorTemplates/Field.cshtml b/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/EditorTemplates/Field.cshtml index 3ff3ecd17..bdc552a4a 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/EditorTemplates/Field.cshtml +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/EditorTemplates/Field.cshtml @@ -2,8 +2,8 @@

    @Model.DisplayName (@Model.FieldDefinition.Name)

    - @Html.ActionLink(T("Edit").Text, "EditField", new { area = "Orchard.ContentTypes", id = Model.Part.Name, Model.Name }, new { itemprop = "RemoveUrl UnsafeUrl" }) | - @Html.ActionLink(T("Remove").Text, "RemoveFieldFrom", new { area = "Orchard.ContentTypes", id = Model.Part.Name, Model.Name }, new { itemprop = "RemoveUrl UnsafeUrl" }) @* <- some experimentation *@ + @Html.ActionLink(T("Edit").Text, "EditField", new { area = "Orchard.ContentTypes", id = Model.Part.Name, Model.Name }) | + @Html.ActionLink(T("Remove").Text, "RemoveFieldFrom", new { area = "Orchard.ContentTypes", id = Model.Part.Name, Model.Name }, new { itemprop = "RemoveUrl UnsafeUrl" })
    @{Html.RenderTemplates(Model.Templates);} diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Services/ModuleService.cs b/src/Orchard.Web/Modules/Orchard.Modules/Services/ModuleService.cs index f061e7f6f..3616a9052 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Services/ModuleService.cs +++ b/src/Orchard.Web/Modules/Orchard.Modules/Services/ModuleService.cs @@ -72,7 +72,7 @@ namespace Orchard.Modules.Services { /// Boolean parameter indicating if the feature should enable it's dependencies if required or fail otherwise. public void EnableFeatures(IEnumerable featureIds, bool force) { foreach (string featureId in _featureManager.EnableFeatures(featureIds, force)) { - var featureName = _featureManager.GetAvailableFeatures().Where(f => f.Id == featureId).First().Name; + var featureName = _featureManager.GetAvailableFeatures().First(f => f.Id.Equals(featureId, StringComparison.OrdinalIgnoreCase)).Name; Services.Notifier.Information(T("{0} was enabled", featureName)); } } diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.cshtml b/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.cshtml index 1dafa7c77..13de78eb5 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Features.cshtml @@ -49,16 +49,17 @@ } var dependencies = (from d in feature.Descriptor.Dependencies - select (from f in Model.Features where f.Descriptor.Id == d select f).SingleOrDefault()).Where(f => f != null).OrderBy(f => f.Descriptor.Name); + select (from f in Model.Features where f.Descriptor.Id.Equals(d, StringComparison.OrdinalIgnoreCase) select f).SingleOrDefault()).Where(f => f != null).OrderBy(f => f.Descriptor.Name); var missingDependencies = feature.Descriptor.Dependencies - .Where(d => !Model.Features.Any(f => f.Descriptor.Id == d)); + .Where(d => !Model.Features.Any(f => f.Descriptor.Id.Equals(d, StringComparison.OrdinalIgnoreCase))); showDisable = categoryName.ToString() != "Core"; - showEnable = missingDependencies.Count() == 0 && feature.Descriptor.Id != "Orchard.Setup"; + showEnable = !missingDependencies.Any() && feature.Descriptor.Id != "Orchard.Setup";
  • @featureName

    -

    @feature.Descriptor.Description

    @if (feature.Descriptor.Dependencies != null && feature.Descriptor.Dependencies.Any()) { +

    @feature.Descriptor.Description

    + @if (feature.Descriptor.Dependencies != null && feature.Descriptor.Dependencies.Any()) {

    @T("Depends on:")

    @Html.UnorderedList(dependencies, diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/TenantService.cs b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/TenantService.cs index 0e6a33ee6..08e7376ee 100644 --- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/TenantService.cs +++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Services/TenantService.cs @@ -48,7 +48,7 @@ namespace Orchard.MultiTenancy.Services { ExtensionDescriptor theme = descriptor; - if (!theme.Tags.Contains("hidden")) { + if (theme.Tags == null || !theme.Tags.Contains("hidden")) { themes.Add(theme); } } diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/PackagingServicesController.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/PackagingServicesController.cs index 3cbb0c6c6..71bf40589 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/PackagingServicesController.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/PackagingServicesController.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Web.Hosting; using System.Web.Mvc; +using System.Xml.Linq; using NuGet; using Orchard.Environment.Configuration; using Orchard.Environment.Extensions.Models; @@ -227,8 +228,17 @@ namespace Orchard.Packaging.Controllers { .Where(feature => feature.Enable) .Select(feature => feature.FeatureDescriptor.Id); - // Enable the features and its dependencies - _moduleService.EnableFeatures(featureIds, true); + // Enable the features and its dependencies using recipes, so that they are run after the module's recipes + + var recipe = new Recipe { + RecipeSteps = featureIds.Select( + x => new RecipeStep { + Name = "Feature", + Step = new XElement("Feature", new XAttribute("enable", x)) + }) + }; + + _recipeManager.Execute(recipe); } } catch (Exception exception) { Services.Notifier.Error(T("Post installation steps failed with error: {0}", exception.Message)); diff --git a/src/Orchard.Web/Modules/Orchard.PublishLater/Views/Parts/PublishLater.Metadata.SummaryAdmin.cshtml b/src/Orchard.Web/Modules/Orchard.PublishLater/Views/Parts/PublishLater.Metadata.SummaryAdmin.cshtml index 1eb13bfe2..4ed1d302e 100644 --- a/src/Orchard.Web/Modules/Orchard.PublishLater/Views/Parts/PublishLater.Metadata.SummaryAdmin.cshtml +++ b/src/Orchard.Web/Modules/Orchard.PublishLater/Views/Parts/PublishLater.Metadata.SummaryAdmin.cshtml @@ -30,7 +30,7 @@ } else { @T( @T("Scheduled") - @Html.DateTime(((DateTime?)Model.ScheduledPublishUtc).Value.ToLocalTime(), T("M/d/yyyy h:mm tt")) + @Display.DateTime(((DateTime?)Model.ScheduledPublishUtc).Value.ToLocalTime(), T("M/d/yyyy h:mm tt")) } | 
  • }
\ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs index 4db4bc375..e33154734 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs @@ -188,6 +188,10 @@ namespace Orchard.Setup { public string BaseUrl { get { return ""; } } + + public string SiteTimeZone { + get { return TimeZoneInfo.Local.Id; } + } } } } diff --git a/src/Orchard.Web/Orchard.Web.csproj b/src/Orchard.Web/Orchard.Web.csproj index 37ae386a1..4f7b7f18b 100644 --- a/src/Orchard.Web/Orchard.Web.csproj +++ b/src/Orchard.Web/Orchard.Web.csproj @@ -50,10 +50,6 @@ False ..\..\lib\autofac\Autofac.Integration.Web.dll - - False - ..\..\lib\autofac\Autofac.Integration.Web.Mvc.dll - False ..\..\lib\aspnetmvc\Microsoft.Web.Infrastructure.dll diff --git a/src/Orchard/ContentManagement/DefaultHqlQuery.cs b/src/Orchard/ContentManagement/DefaultHqlQuery.cs index c272482e3..4228fe015 100644 --- a/src/Orchard/ContentManagement/DefaultHqlQuery.cs +++ b/src/Orchard/ContentManagement/DefaultHqlQuery.cs @@ -193,27 +193,22 @@ namespace Orchard.ContentManagement { sb.Append(join.Item2.Type).Append(" ").Append(join.Item1.Name + "." + join.Item2.TableName).Append(" as ").Append(join.Item2.Name).AppendLine(); } - #region Where + // generating where clause + if (_wheres.Any()) { + sb.Append("where "); - bool first = true; - foreach (var where in _wheres) { - if (!first) { - sb.Append("and "); - } - else { - sb.Append("where "); - first = false; + var expressions = new List(); + + foreach (var where in _wheres) { + var expressionFactory = new DefaultHqlExpressionFactory(); + where.Item2(expressionFactory); + expressions.Add(expressionFactory.Criterion.ToHql(where.Item1)); } - var expressionFactory = new DefaultHqlExpressionFactory(); - where.Item2(expressionFactory); - sb.Append(expressionFactory.Criterion.ToHql(where.Item1)).AppendLine(); + sb.Append("(").Append(String.Join(") AND (", expressions.ToArray())).Append(")").AppendLine(); } - #endregion - - #region Order by - + // generating order by clause bool firstSort = true; foreach (var sort in _sortings) { if (!firstSort) { @@ -238,8 +233,6 @@ namespace Orchard.ContentManagement { } } - #endregion - return sb.ToString(); } diff --git a/src/Orchard/Environment/AutofacUtil/LifetimeScopeContainer.cs b/src/Orchard/Environment/AutofacUtil/LifetimeScopeContainer.cs index ed8acc95a..9533ee180 100644 --- a/src/Orchard/Environment/AutofacUtil/LifetimeScopeContainer.cs +++ b/src/Orchard/Environment/AutofacUtil/LifetimeScopeContainer.cs @@ -48,8 +48,17 @@ namespace Orchard.Environment.AutofacUtil { get { return _lifetimeScope.Tag; } } - public event EventHandler ChildLifetimeScopeBeginning; - public event EventHandler CurrentScopeEnding; - public event EventHandler ResolveOperationBeginning; + event EventHandler ILifetimeScope.ChildLifetimeScopeBeginning { + add { _lifetimeScope.ChildLifetimeScopeBeginning += value; } + remove { _lifetimeScope.ChildLifetimeScopeBeginning -= value; } + } + event EventHandler ILifetimeScope.CurrentScopeEnding { + add { _lifetimeScope.CurrentScopeEnding += value; } + remove { _lifetimeScope.CurrentScopeEnding -= value; } + } + event EventHandler ILifetimeScope.ResolveOperationBeginning { + add { _lifetimeScope.ResolveOperationBeginning += value; } + remove { _lifetimeScope.ResolveOperationBeginning -= value; } + } } } \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Compilers/DefaultExtensionCompiler.cs b/src/Orchard/Environment/Extensions/Compilers/DefaultExtensionCompiler.cs index c7a9e1366..a45c01469 100644 --- a/src/Orchard/Environment/Extensions/Compilers/DefaultExtensionCompiler.cs +++ b/src/Orchard/Environment/Extensions/Compilers/DefaultExtensionCompiler.cs @@ -80,6 +80,8 @@ namespace Orchard.Environment.Extensions.Compilers { context.AssemblyBuilder.AddAssemblyReference(assembly); } + _criticalErrorProvider.Clear(); + // Load references specified in project file (only the ones not yet loaded) foreach (var assemblyReference in projectFileDescriptor.References) { if (addedReferences.Contains(assemblyReference.SimpleName)) diff --git a/src/Orchard/Environment/Extensions/DefaultCriticalErrorProvider.cs b/src/Orchard/Environment/Extensions/DefaultCriticalErrorProvider.cs index 30272f38a..22bc97830 100644 --- a/src/Orchard/Environment/Extensions/DefaultCriticalErrorProvider.cs +++ b/src/Orchard/Environment/Extensions/DefaultCriticalErrorProvider.cs @@ -5,7 +5,8 @@ using Orchard.Localization; namespace Orchard.Environment.Extensions { public class DefaultCriticalErrorProvider : ICriticalErrorProvider { - private readonly ConcurrentBag _errorMessages; + private ConcurrentBag _errorMessages; + private readonly object _synLock = new object(); public DefaultCriticalErrorProvider() { _errorMessages = new ConcurrentBag(); @@ -22,5 +23,11 @@ namespace Orchard.Environment.Extensions { } } + public void Clear() { + lock (_synLock) { + _errorMessages = new ConcurrentBag(); + } + + } } } diff --git a/src/Orchard/Environment/Extensions/ICriticalErrorProvider.cs b/src/Orchard/Environment/Extensions/ICriticalErrorProvider.cs index 23dc5aec6..43c722d40 100644 --- a/src/Orchard/Environment/Extensions/ICriticalErrorProvider.cs +++ b/src/Orchard/Environment/Extensions/ICriticalErrorProvider.cs @@ -9,5 +9,11 @@ namespace Orchard.Environment.Extensions { /// Called by any to notice the system of a critical issue at the system level, e.g. incorrect extensions /// void RegisterErrorMessage(LocalizedString message); + + /// + /// Removes all error message + /// + void Clear(); + } } diff --git a/src/Orchard/Environment/Features/FeatureManager.cs b/src/Orchard/Environment/Features/FeatureManager.cs index 0085ba434..993fd8f7d 100644 --- a/src/Orchard/Environment/Features/FeatureManager.cs +++ b/src/Orchard/Environment/Features/FeatureManager.cs @@ -109,13 +109,13 @@ namespace Orchard.Environment.Features { IDictionary availableFeatures = GetAvailableFeatures() .ToDictionary(featureDescriptor => featureDescriptor, - featureDescriptor => enabledFeatures.FirstOrDefault(shellFeature => shellFeature.Name == featureDescriptor.Id) != null); + featureDescriptor => enabledFeatures.FirstOrDefault(shellFeature => shellFeature.Name.Equals(featureDescriptor.Id)) != null); IEnumerable featuresToDisable = featureIds .Select(featureId => DisableFeature(featureId, availableFeatures, force)).ToList() .SelectMany(ies => ies.Select(s => s)); - if (featuresToDisable.Count() > 0) { + if (featuresToDisable.Any()) { foreach (string featureId in featuresToDisable) { string id = featureId; @@ -141,11 +141,11 @@ namespace Orchard.Environment.Features { var getDisabledDependencies = new Func, IDictionary>( (currentFeatureId, featuresState) => { - KeyValuePair feature = featuresState.Single(featureState => featureState.Key.Id == currentFeatureId); + KeyValuePair feature = featuresState.Single(featureState => featureState.Key.Id.Equals(currentFeatureId, StringComparison.OrdinalIgnoreCase)); // Retrieve disabled dependencies for the current feature return feature.Key.Dependencies - .Select(fId => featuresState.Single(featureState => featureState.Key.Id == fId)) + .Select(fId => featuresState.Single(featureState => featureState.Key.Id.Equals(fId, StringComparison.OrdinalIgnoreCase))) .Where(featureState => !featureState.Value) .ToDictionary(f => f.Key, f => f.Value); }); @@ -173,7 +173,7 @@ namespace Orchard.Environment.Features { private IEnumerable DisableFeature(string featureId, IDictionary availableFeatures, bool force) { var getEnabledDependants = new Func, IDictionary>( - (currentFeatureId, fs) => fs.Where(f => f.Value && f.Key.Dependencies != null && f.Key.Dependencies.Contains(currentFeatureId)) + (currentFeatureId, fs) => fs.Where(f => f.Value && f.Key.Dependencies != null && f.Key.Dependencies.Select(s => s.ToLowerInvariant()).Contains(currentFeatureId.ToLowerInvariant())) .ToDictionary(f => f.Key, f => f.Value)); IEnumerable featuresToDisable = GetAffectedFeatures(featureId, availableFeatures, getEnabledDependants); diff --git a/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs b/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs index 9750c0c7c..90d4140f5 100644 --- a/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs +++ b/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs @@ -165,28 +165,6 @@ namespace Orchard.Mvc.Html { #endregion - #region Format Date/Time - - public static LocalizedString DateTime(this HtmlHelper htmlHelper, DateTime? value, LocalizedString defaultIfNull) { - return value.HasValue ? htmlHelper.DateTime(value.Value) : defaultIfNull; - } - - public static LocalizedString DateTime(this HtmlHelper htmlHelper, DateTime? value, LocalizedString defaultIfNull, LocalizedString customFormat) { - return value.HasValue ? htmlHelper.DateTime(value.Value, customFormat) : defaultIfNull; - } - - public static LocalizedString DateTime(this HtmlHelper htmlHelper, DateTime value) { - //TODO: (erikpo) This default format should come from a site setting - return htmlHelper.DateTime(value.ToLocalTime(), new LocalizedString("MMM d yyyy h:mm tt")); //todo: above comment and get rid of just wrapping this as a localized string - } - - public static LocalizedString DateTime(this HtmlHelper htmlHelper, DateTime value, LocalizedString customFormat) { - //TODO: (erikpo) In the future, convert this to "local" time before calling ToString - return new LocalizedString(value.ToString(customFormat.Text)); - } - - #endregion - #region Image public static MvcHtmlString Image(this HtmlHelper htmlHelper, string src, string alt, object htmlAttributes) { diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 30d07d3c2..2cb0cbc40 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -68,11 +68,6 @@ ..\..\lib\autofac\Autofac.Integration.Web.dll True - - False - ..\..\lib\autofac\Autofac.Integration.Web.Mvc.dll - True - False ..\..\lib\Castle Windsor 2.0\bin\Castle.Core.dll @@ -276,10 +271,16 @@ + + + + + + @@ -884,7 +885,7 @@ - + diff --git a/src/Orchard/Services/Clock.cs b/src/Orchard/Services/Clock.cs index e72f7fae1..692768617 100644 --- a/src/Orchard/Services/Clock.cs +++ b/src/Orchard/Services/Clock.cs @@ -2,20 +2,6 @@ using Orchard.Caching; namespace Orchard.Services { - public interface IClock : IVolatileProvider { - DateTime UtcNow { get; } - - /// - /// Each retrieved value is cached during the specified amount of time. - /// - IVolatileToken When(TimeSpan duration); - - /// - /// The cache is active until the specified time. Each subsequent access won't be cached. - /// - IVolatileToken WhenUtc(DateTime absoluteUtc); - } - public class Clock : IClock { public DateTime UtcNow { get { return DateTime.UtcNow; } diff --git a/src/Orchard/Services/IClock.cs b/src/Orchard/Services/IClock.cs new file mode 100644 index 000000000..3560a9318 --- /dev/null +++ b/src/Orchard/Services/IClock.cs @@ -0,0 +1,56 @@ +using System; +using Orchard.Caching; + +namespace Orchard.Services { + /// + /// Provides the current Utc , and time related method for cache management. + /// This service should be used whenever the current date and time are needed, instead of directly. + /// It also makes implementations more testable, as time can be mocked. + /// + public interface IClock : IVolatileProvider { + /// + /// Gets the current of the system, expressed in Utc + /// + DateTime UtcNow { get; } + + /// + /// Provides a instance which can be used to cache some information for a + /// specific duration. + /// + /// The duration that the token must be valid. + /// + /// This sample shows how to use the method by returning the result of + /// a method named LoadVotes(), which is computed every 10 minutes only. + /// + /// _cacheManager.Get("votes", + /// ctx => { + /// ctx.Monitor(_clock.When(TimeSpan.FromMinutes(10))); + /// return LoadVotes(); + /// }); + /// + /// + IVolatileToken When(TimeSpan duration); + + /// + /// Provides a instance which can be used to cache some + /// until a specific date and time. + /// + /// The date and time that the token must be valid until. + /// + /// This sample shows how to use the method by returning the result of + /// a method named LoadVotes(), which is computed once, and no more until the end of the year. + /// + /// var endOfYear = _clock.UtcNow; + /// endOfYear.Month = 12; + /// endOfYear.Day = 31; + /// + /// _cacheManager.Get("votes", + /// ctx => { + /// ctx.Monitor(_clock.WhenUtc(endOfYear)); + /// return LoadVotes(); + /// }); + /// + /// + IVolatileToken WhenUtc(DateTime absoluteUtc); + } +} diff --git a/src/Orchard/Settings/ISite.cs b/src/Orchard/Settings/ISite.cs index e37d47e63..666a58f46 100644 --- a/src/Orchard/Settings/ISite.cs +++ b/src/Orchard/Settings/ISite.cs @@ -1,4 +1,5 @@ -using Orchard.ContentManagement; +using System; +using Orchard.ContentManagement; namespace Orchard.Settings { /// @@ -14,5 +15,6 @@ namespace Orchard.Settings { ResourceDebugMode ResourceDebugMode { get; set; } int PageSize { get; set; } string BaseUrl { get; } + string SiteTimeZone { get; } } } diff --git a/src/Orchard/Time/CurrentTimeZoneWorkContext.cs b/src/Orchard/Time/CurrentTimeZoneWorkContext.cs new file mode 100644 index 000000000..8951ea2b7 --- /dev/null +++ b/src/Orchard/Time/CurrentTimeZoneWorkContext.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Orchard.Time { + public class CurrentTimeZoneWorkContext : IWorkContextStateProvider { + private readonly IEnumerable _timeZoneSelectors; + + public CurrentTimeZoneWorkContext(IEnumerable timeZoneSelectors) { + _timeZoneSelectors = timeZoneSelectors; + } + + public Func Get(string name) { + if (name == "CurrentTimeZone") { + return ctx => (T)(object)CurrentTimeZone(ctx.HttpContext); + } + return null; + } + + TimeZoneInfo CurrentTimeZone(HttpContextBase httpContext) { + var timeZone = _timeZoneSelectors + .Select(x => x.GetTimeZone(httpContext)) + .Where(x => x != null) + .OrderByDescending(x => x.Priority) + .FirstOrDefault(); + + if (timeZone == null) { + return null; + } + + return timeZone.TimeZone; + } + } +} diff --git a/src/Orchard/Time/FallbackTimeZoneSelector.cs b/src/Orchard/Time/FallbackTimeZoneSelector.cs new file mode 100644 index 000000000..180bb3e37 --- /dev/null +++ b/src/Orchard/Time/FallbackTimeZoneSelector.cs @@ -0,0 +1,16 @@ +using System; +using System.Web; + +namespace Orchard.Time { + /// + /// Implements by providing the timezone defined in the machine's local settings. + /// + public class FallbackTimeZoneSelector : ITimeZoneSelector { + public TimeZoneSelectorResult GetTimeZone(HttpContextBase context) { + return new TimeZoneSelectorResult { + Priority = -100, + TimeZone = TimeZoneInfo.Local + }; + } + } +} \ No newline at end of file diff --git a/src/Orchard/Time/ITimeZoneSelector.cs b/src/Orchard/Time/ITimeZoneSelector.cs new file mode 100644 index 000000000..ce341ecda --- /dev/null +++ b/src/Orchard/Time/ITimeZoneSelector.cs @@ -0,0 +1,7 @@ +using System.Web; + +namespace Orchard.Time { + public interface ITimeZoneSelector : IDependency { + TimeZoneSelectorResult GetTimeZone(HttpContextBase context); + } +} diff --git a/src/Orchard/Time/SiteTimeZoneSelector.cs b/src/Orchard/Time/SiteTimeZoneSelector.cs new file mode 100644 index 000000000..300d5548e --- /dev/null +++ b/src/Orchard/Time/SiteTimeZoneSelector.cs @@ -0,0 +1,28 @@ +using System; +using System.Web; + +namespace Orchard.Time { + /// + /// Implements by providing the timezone defined in the sites settings. + /// + public class SiteTimeZoneSelector : ITimeZoneSelector { + private readonly IWorkContextAccessor _workContextAccessor; + + public SiteTimeZoneSelector(IWorkContextAccessor workContextAccessor) { + _workContextAccessor = workContextAccessor; + } + + public TimeZoneSelectorResult GetTimeZone(HttpContextBase context) { + var siteTimeZoneId = _workContextAccessor.GetContext(context).CurrentSite.SiteTimeZone; + + if (String.IsNullOrEmpty(siteTimeZoneId)) { + return null; + } + + return new TimeZoneSelectorResult { + Priority = -5, + TimeZone = TimeZoneInfo.FindSystemTimeZoneById(siteTimeZoneId) + }; + } + } +} diff --git a/src/Orchard/Time/TimeZoneSelectorResult.cs b/src/Orchard/Time/TimeZoneSelectorResult.cs new file mode 100644 index 000000000..3a34731a0 --- /dev/null +++ b/src/Orchard/Time/TimeZoneSelectorResult.cs @@ -0,0 +1,8 @@ +using System; + +namespace Orchard.Time { + public class TimeZoneSelectorResult { + public int Priority { get; set; } + public TimeZoneInfo TimeZone { get; set; } + } +} diff --git a/src/Orchard/UI/Resources/ResourceDefinition.cs b/src/Orchard/UI/Resources/ResourceDefinition.cs index 1c9965d19..f900661a1 100644 --- a/src/Orchard/UI/Resources/ResourceDefinition.cs +++ b/src/Orchard/UI/Resources/ResourceDefinition.cs @@ -192,7 +192,7 @@ namespace Orchard.UI.Resources { if (!String.IsNullOrEmpty(settings.Culture)) { string nearestCulture = FindNearestCulture(settings.Culture); if (!String.IsNullOrEmpty(nearestCulture)) { - url = Path.ChangeExtension(url, nearestCulture + "." + Path.GetExtension(url)); + url = Path.ChangeExtension(url, nearestCulture + Path.GetExtension(url)); } } if (!Uri.IsWellFormedUriString(url, UriKind.Absolute) && !VirtualPathUtility.IsAbsolute(url) && !VirtualPathUtility.IsAppRelative(url) && !String.IsNullOrEmpty(BasePath)) { diff --git a/src/Orchard/WorkContext.cs b/src/Orchard/WorkContext.cs index e5b5f1540..28910debe 100644 --- a/src/Orchard/WorkContext.cs +++ b/src/Orchard/WorkContext.cs @@ -1,4 +1,5 @@ -using System.Web; +using System; +using System.Web; using Orchard.Environment.Extensions.Models; using Orchard.Security; using Orchard.Settings; @@ -75,5 +76,13 @@ namespace Orchard { get { return GetState("CurrentCulture"); } set { SetState("CurrentCulture", value); } } + + /// + /// Time zone of the work context + /// + public TimeZoneInfo CurrentTimeZone { + get { return GetState("CurrentTimeZone"); } + set { SetState("CurrentTimeZone", value); } + } } }